diff --git a/examples/secretsdump.py b/examples/secretsdump.py index 1ab5616ed..b43492f8a 100755 --- a/examples/secretsdump.py +++ b/examples/secretsdump.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # Impacket - Collection of Python classes for working with network protocols. # -# Copyright Fortra, LLC and its affiliated companies +# Copyright Fortra, LLC and its affiliated companies # # All rights reserved. # @@ -72,6 +72,7 @@ except NameError: pass + class DumpSecrets: def __init__(self, remoteName, username='', password='', domain='', options=None): self.__useVSSMethod = options.use_vss @@ -111,7 +112,7 @@ def __init__(self, remoteName, username='', password='', domain='', options=None self.__ldapFilter = options.ldapfilter self.__skipUser = options.skip_user self.__pwdLastSet = options.pwd_last_set - self.__printUserStatus= options.user_status + self.__printUserStatus = options.user_status self.__resumeFileName = options.resumefile self.__canProcessSAMLSA = True self.__kdcHost = options.dc_ip @@ -215,7 +216,7 @@ def dump(self): localOperations = LocalOperations(self.__systemHive) bootKey = localOperations.getBootKey() if self.__ntdsFile is not None: - # Let's grab target's configuration about LM Hashes storage + # Let's grab target's configuration about LM Hashes storage self.__noLMHash = localOperations.checkNoLMHashPolicy() else: import binascii @@ -243,7 +244,7 @@ def dump(self): else: raise - self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost, self.__ldapConnection) + self.__remoteOps = RemoteOperations(self.__smbConnection, self.__doKerberos, self.__kdcHost, self.__ldapConnection) self.__remoteOps.setExecMethod(self.__options.exec_method) if self.__justDC is False and self.__justDCNTLM is False and self.__useKeyListMethod is False or self.__useVSSMethod is True: self.__remoteOps.enableRegistry() @@ -253,7 +254,7 @@ def dump(self): except Exception as e: self.__canProcessSAMLSA = False if str(e).find('STATUS_USER_SESSION_DELETED') and os.getenv('KRB5CCNAME') is not None \ - and self.__doKerberos is True: + and self.__doKerberos is True: # Giving some hints here when SPN target name validation is set to something different to Off # This will prevent establishing SMB connections using TGS for SPNs different to cifs/ logging.error('Policy SPN target name validation might be restricting full DRSUAPI dump. Try -just-dc-user') @@ -276,8 +277,7 @@ def dump(self): SAMFileName = self.__remoteOps.saveSAM() else: SAMFileName = self.__samHive - - self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote = self.__isRemote) + self.__SAMHashes = SAMHashes(SAMFileName, bootKey, isRemote=self.__isRemote, printUserStatus=self.__printUserStatus) self.__SAMHashes.dump() if self.__outputFileName is not None: self.__SAMHashes.export(self.__outputFileName) @@ -292,7 +292,7 @@ def dump(self): SECURITYFileName = self.__securityHive self.__LSASecrets = LSASecrets(SECURITYFileName, bootKey, self.__remoteOps, - isRemote=self.__isRemote, history=self.__history) + isRemote=self.__isRemote, history=self.__history) self.__LSASecrets.dumpCachedHashes() if self.__outputFileName is not None: self.__LSASecrets.exportCached(self.__outputFileName) @@ -318,7 +318,7 @@ def dump(self): noLMHash=self.__noLMHash, remoteOps=self.__remoteOps, useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, - outputFileName=self.__outputFileName, justUser=self.__justUser, + outputFileName=self.__outputFileName, justUser=self.__justUser, skipUser=self.__skipUser, ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus) try: @@ -349,7 +349,7 @@ def dump(self): if self.__NTDSHashes is not None: if isinstance(e, KeyboardInterrupt): while True: - answer = input("Delete resume session file? [y/N] ") + answer = input("Delete resume session file? [y/N] ") if answer.upper() == '': answer = 'N' break @@ -391,8 +391,8 @@ def cleanup(self): print(version.BANNER) - parser = argparse.ArgumentParser(add_help = True, description = "Performs various techniques to dump secrets from " - "the remote machine without executing any agent there.") + parser = argparse.ArgumentParser(add_help=True, description="Performs various techniques to dump secrets from " + "the remote machine without executing any agent there.") parser.add_argument('target', action='store', help='[[domain/]username[:password]@] or LOCAL' ' (if you want to parse local files)') @@ -404,8 +404,8 @@ def cleanup(self): parser.add_argument('-sam', action='store', help='SAM hive to parse') parser.add_argument('-ntds', action='store', help='NTDS.DIT file to parse') parser.add_argument('-resumefile', action='store', help='resume file name to resume NTDS.DIT session dump (only ' - 'available to DRSUAPI approach). This file will also be used to keep updating the session\'s ' - 'state') + 'available to DRSUAPI approach). This file will also be used to keep updating the session\'s ' + 'state') parser.add_argument('-skip-sam', action='store_true', help='Do NOT parse the SAM hive on remote system') parser.add_argument('-skip-security', action='store_true', help='Do NOT parse the SECURITY hive on remote system') parser.add_argument('-outputfile', action='store', @@ -433,35 +433,35 @@ def cleanup(self): help='Extract only NTDS.DIT data for specific users based on an LDAP filter. ' 'Only available for DRSUAPI approach. Implies also -just-dc switch') group.add_argument('-just-dc', action='store_true', default=False, - help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)') + help='Extract only NTDS.DIT data (NTLM hashes and Kerberos keys)') group.add_argument('-just-dc-ntlm', action='store_true', default=False, help='Extract only NTDS.DIT data (NTLM hashes only)') group.add_argument('-skip-user', action='store', help='Do NOT extract NTDS.DIT data for the user specified. ' - 'Can provide comma-separated list of users to skip, or text file with one user per line') + 'Can provide comma-separated list of users to skip, or text file with one user per line') group.add_argument('-pwd-last-set', action='store_true', default=False, help='Shows pwdLastSet attribute for each NTDS.DIT account. Doesn\'t apply to -outputfile data') group.add_argument('-user-status', action='store_true', default=False, - help='Display whether or not the user is disabled') + help='Display whether or not the user is disabled') group.add_argument('-history', action='store_true', help='Dump password history, and LSA secrets OldVal') group = parser.add_argument_group('authentication') - group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') + group.add_argument('-hashes', action="store", metavar="LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' - '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use' - ' the ones specified in the command line') - group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication' - ' (128 or 256 bits)') + '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use' + ' the ones specified in the command line') + group.add_argument('-aesKey', action="store", metavar="hex key", help='AES key to use for Kerberos Authentication' + ' (128 or 256 bits)') group.add_argument('-keytab', action="store", help='Read keys for SPN from keytab file') group = parser.add_argument_group('connection') - group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' - 'ommited it use the domain part (FQDN) specified in the target parameter') + group.add_argument('-dc-ip', action='store', metavar="ip address", help='IP Address of the domain controller. If ' + 'ommited it use the domain part (FQDN) specified in the target parameter') group.add_argument('-target-ip', action='store', metavar="ip address", help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 'This is useful when target is the NetBIOS name and you cannot resolve it') - if len(sys.argv)==1: + if len(sys.argv) == 1: parser.print_help() sys.exit(1) diff --git a/impacket/examples/secretsdump.py b/impacket/examples/secretsdump.py index 43b776218..8dfa579bf 100644 --- a/impacket/examples/secretsdump.py +++ b/impacket/examples/secretsdump.py @@ -162,7 +162,7 @@ class DOMAIN_ACCOUNT_F(Structure): ('MaxPasswordAge',' 12: + rev = binary_data[0] + authid = hexlify(binary_data[2:8]).decode().lstrip("0") + sub = "-".join(map(str, unpack("= LockoutThreshold + + if locked: # Let's check if the LockoutDuration has passed. + lockout_expiry_time = LastIncorrectPasswordTimestamp_datetime + timedelta(minutes=LockoutDurationMinutes) + now = datetime.utcnow() + locked = now < lockout_expiry_time # Compare current time with lockout expiry + + grouped_data = userAccountF['GroupedData'] + disabled = bool(grouped_data & 0x0001) + auto_locked = bool(grouped_data & 0x0400) + locked_out = locked - userName = V[userAccount['NameOffset']:userAccount['NameOffset']+userAccount['NameLength']].decode('utf-16le') + userAccount = USER_ACCOUNT_V(self.getValue(ntpath.join(usersKey, rid, 'V'))[1]) + rid = int(rid, 16) + + V = userAccount['Data'] + userName = V[userAccount['NameOffset']:userAccount['NameOffset'] + userAccount['NameLength']].decode('utf-16le') if userAccount['NTHashLength'] == 0: logging.error('SAM hashes extraction for user %s failed. The account doesn\'t have hash information.' % userName) @@ -1467,6 +1624,10 @@ def dump(self): ntHash = ntlm.NTOWFv1('','') answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash).decode('utf-8'), hexlify(ntHash).decode('utf-8')) + + if self.__printUserStatus is True: + answer = f"{answer} (Enabled={'False' if disabled else 'True'}) (Locked={'True' if locked_out or auto_locked else 'False'}) (Admin={'True' if is_admin else 'False'})" + self.__itemsFound[rid] = answer self.__perSecretCallback(answer)