From 95da356e3ba1c65561c5fe7c0456150392fcabb7 Mon Sep 17 00:00:00 2001 From: mpgn Date: Sat, 15 Apr 2023 09:41:49 -0400 Subject: [PATCH] Add new Windows LAPS --- cme/modules/laps.py | 44 +++++++++++++++++++++++++------------------ cme/protocols/smb.py | 45 +++++++++++++++++++++++++++++--------------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/cme/modules/laps.py b/cme/modules/laps.py index be980275..e5eb7907 100644 --- a/cme/modules/laps.py +++ b/cme/modules/laps.py @@ -11,9 +11,7 @@ class CMEModule: Initial module: @T3KX: https://github.com/T3KX/Crackmapexec-LAPS - Credit: @n00py1 - Reference: https://www.n00py.io/2020/12/dumping-laps-passwords-from-linux/ - https://github.com/n00py/LAPSDumper + Credit: @mpgn_x64, @n00py1 ''' name = 'laps' @@ -34,20 +32,30 @@ class CMEModule: def on_login(self, context, connection): context.log.info('Getting LAPS Passwords') if self.computer is not None: - searchFilter = '(&(objectCategory=computer)(ms-MCS-AdmPwd=*)(name=' + self.computer + '))' + searchFilter = '(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=' + self.computer + '))' else: - searchFilter = '(&(objectCategory=computer)(ms-MCS-AdmPwd=*))' - attributes = ['ms-MCS-AdmPwd', 'sAMAccountName'] - results = connection.search(searchFilter, attributes, 10000) + searchFilter = '(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*)))' + attributes = ['msLAPS-EncryptedPassword', 'msLAPS-Password', 'ms-MCS-AdmPwd', 'sAMAccountName'] + results = connection.search(searchFilter, attributes, 0) results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)] - - laps_computers = [] - for computer in results: - msMCSAdmPwd = '' - sAMAccountName = '' - values = {str(attr['type']).lower(): str(attr['vals'][0]) for attr in computer['attributes']} - laps_computers.append((values['samaccountname'], values['ms-mcs-admpwd'])) - - laps_computers = sorted(laps_computers, key=lambda x: x[0]) - for sAMAccountName, msMCSAdmPwd in laps_computers: - context.log.highlight("Computer: {:<20} Password: {}".format(sAMAccountName, msMCSAdmPwd)) + if len(results) != 0: + laps_computers = [] + for computer in results: + msMCSAdmPwd = '' + sAMAccountName = '' + values = {str(attr['type']).lower(): str(attr['vals'][0]) for attr in computer['attributes']} + if "mslaps-encryptedpassword" in values: + context.log.error("LAPS password is encrypted and currently CrackMapExec doesn't support the decryption...") + return + elif "mslaps-password" in values: + r = json.loads(values['mslaps-password']) + laps_computers.append((values['samaccountname'], r['n'], r['p'])) + elif "ms-mcs-admpwd" in values: + laps_computers.append((values['samaccountname'], '', values['ms-mcs-admpwd'])) + else: + context.log.error("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password") + laps_computers = sorted(laps_computers, key=lambda x: x[0]) + for sAMAccountName, user, msMCSAdmPwd in laps_computers: + context.log.highlight("Computer: {:<20} User: {:<15} Password: {}".format(sAMAccountName, user, msMCSAdmPwd)) + else: + context.log.error("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password !") diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index 3ce8fea7..df1ead37 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -347,6 +347,7 @@ class smb(connection): self.domain = self.hostname def laps_search(self, username, password, ntlm_hash, domain): + self.logger.extra['protocol'] = "LDAP" self.logger.extra['port'] = "389" ldapco = LDAPConnect(self.domain, "389", self.domain) @@ -371,12 +372,12 @@ class smb(connection): ntlm_hash[0] if ntlm_hash else '' ) if not connection: - logging.debug('LAPS connection failed with account {}'.format(username)) + self.logger.error('LDAP connection failed with account {}'.format(username)) return False - search_filter = '(&(objectCategory=computer)(ms-MCS-AdmPwd=*)(name=' + self.hostname + '))' - attributes = ['ms-MCS-AdmPwd', 'samAccountname'] - result = connection.search( + search_filter = '(&(objectCategory=computer)(|(msLAPS-EncryptedPassword=*)(ms-MCS-AdmPwd=*)(msLAPS-Password=*))(name=' + self.hostname + '))' + attributes = ['msLAPS-EncryptedPassword', 'msLAPS-Password', 'ms-MCS-AdmPwd', 'sAMAccountName'] + results = connection.search( searchFilter=search_filter, attributes=attributes, sizeLimit=0 @@ -384,21 +385,35 @@ class smb(connection): msMCSAdmPwd = '' sAMAccountName = '' - for item in result: - if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: - continue - for host in item['attributes']: - if str(host['type']) == "sAMAccountName": - sAMAccountName = str(host['vals'][0]) - else: - msMCSAdmPwd = str(host['vals'][0]) - logging.debug("Host: {:<20} Password: {} {}".format(sAMAccountName, msMCSAdmPwd, self.hostname)) + username = '' - self.username = self.args.laps + from impacket.ldap import ldapasn1 as ldapasn1_impacket + results = [r for r in results if isinstance(r, ldapasn1_impacket.SearchResultEntry)] + if len(results) != 0: + for host in results: + values = {str(attr['type']).lower(): str(attr['vals'][0]) for attr in host['attributes']} + if "mslaps-encryptedpassword" in values: + self.logger.error("LAPS password is encrypted and currently CrackMapExec doesn't support the decryption...") + return False + elif "mslaps-password" in values: + from json import loads + r = loads(values['mslaps-password']) + msMCSAdmPwd = r['p'] + username = r['n'] + elif "ms-mcs-admpwd" in values: + msMCSAdmPwd = values['ms-mcs-admpwd'] + else: + self.logger.error("No result found with attribute ms-MCS-AdmPwd or msLAPS-Password") + logging.debug("Host: {:<20} Password: {} {}".format(sAMAccountName, msMCSAdmPwd, self.hostname)) + else: + self.logger.error('msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {}'.format(self.hostname)) + return False + + self.username = self.args.laps if not username else username self.password = msMCSAdmPwd if msMCSAdmPwd == '': - self.logger.error('msMCSAdmPwd is empty or account cannot read LAPS property for {}'.format(self.hostname)) + self.logger.error('msMCSAdmPwd or msLAPS-Password is empty or account cannot read LAPS property for {}'.format(self.hostname)) return False if ntlm_hash: hash_ntlm = hashlib.new('md4', msMCSAdmPwd.encode('utf-16le')).digest()