diff --git a/examples/secretsdump.py b/examples/secretsdump.py index 2062cbb87..cb3bad96e 100755 --- a/examples/secretsdump.py +++ b/examples/secretsdump.py @@ -110,6 +110,7 @@ def __init__(self, remoteName, username='', password='', domain='', options=None self.__resumeFileName = options.resumefile self.__canProcessSAMLSA = True self.__kdcHost = options.dc_ip + self.__dumpTdo = options.dump_tdo self.__options = options if options.hashes is not None: @@ -274,7 +275,7 @@ def dump(self): useVSSMethod=self.__useVSSMethod, justNTLM=self.__justDCNTLM, pwdLastSet=self.__pwdLastSet, resumeSession=self.__resumeFileName, outputFileName=self.__outputFileName, justUser=self.__justUser, - ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus) + ldapFilter=self.__ldapFilter, printUserStatus=self.__printUserStatus, dumpTdo=self.__dumpTdo) try: self.__NTDSHashes.dump() except Exception as e: @@ -364,6 +365,8 @@ def cleanup(self): help='base output filename. Extensions will be added for sam, secrets, cached and ntds') parser.add_argument('-use-vss', action='store_true', default=False, help='Use the VSS method instead of default DRSUAPI') + parser.add_argument('-dump-tdo', action='store_true', default=False, + help='Try to dump CLEAR trusts password (LOCAL NTDS only)') parser.add_argument('-rodcNo', action='store', type=int, help='Number of the RODC krbtgt account (only avaiable for Kerb-Key-List approach)') parser.add_argument('-rodcKey', action='store', help='AES key of the Read Only Domain Controller (only avaiable for Kerb-Key-List approach)') parser.add_argument('-use-keylist', action='store_true', default=False, @@ -437,6 +440,11 @@ def cleanup(self): # Having this switch on implies not asking for anything else. options.just_dc = True + if options.dump_tdo is True: + if remoteName.upper() != 'LOCAL': + logging.error('-dump-tdo not compatible with remote target') + sys.exit(1) + if options.use_vss is True and options.resumefile is not None: logging.error('resuming a previous NTDS.DIT dump session is not supported in VSS mode') sys.exit(1) diff --git a/impacket/examples/secretsdump.py b/impacket/examples/secretsdump.py index 8351cfb2a..188c98159 100644 --- a/impacket/examples/secretsdump.py +++ b/impacket/examples/secretsdump.py @@ -62,6 +62,7 @@ from datetime import datetime, timedelta from struct import unpack, pack from six import b, PY2 +from base64 import b64encode from impacket import LOG from impacket import system_errors @@ -1867,6 +1868,11 @@ class SECRET_TYPE: 'pekList':b'ATTk590689', 'supplementalCredentials':b'ATTk589949', 'pwdLastSet':b'ATTq589920', + + + 'trustAuthIncoming':b'ATTk589953', + 'trustAuthOutgoing':b'ATTk589959', + 'trustPartner':b'ATTm589957' } NAME_TO_ATTRTYP = { @@ -1963,7 +1969,7 @@ def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=Tr useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None, justUser=None, ldapFilter=None, printUserStatus=False, perSecretCallback = lambda secretType, secret : _print_helper(secret), - resumeSessionMgr=ResumeSessionMgrInFile): + resumeSessionMgr=ResumeSessionMgrInFile, dumpTdo=False): self.__bootKey = bootKey self.__NTDS = ntdsFile self.__history = history @@ -1979,6 +1985,7 @@ def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=Tr self.__PEK = list() self.__cryptoCommon = CryptoCommon() self.__kerberosKeys = OrderedDict() + self.__tdoSecrets = OrderedDict() self.__clearTextPwds = OrderedDict() self.__justNTLM = justNTLM self.__resumeSession = resumeSessionMgr(resumeSession) @@ -1986,10 +1993,12 @@ def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=Tr self.__justUser = justUser self.__ldapFilter = ldapFilter self.__perSecretCallback = perSecretCallback + self.__dumpTdo = dumpTdo # these are all the columns that we need to get the secrets. # If in the future someone finds other columns containing interesting things please extend ths table. self.__filter_tables_usersecret = { + self.NAME_TO_INTERNAL['objectGUID'] : 1, self.NAME_TO_INTERNAL['objectSid'] : 1, self.NAME_TO_INTERNAL['dBCSPwd'] : 1, self.NAME_TO_INTERNAL['name'] : 1, @@ -2004,6 +2013,9 @@ def __init__(self, ntdsFile, bootKey, isRemote=False, history=False, noLMHash=Tr self.NAME_TO_INTERNAL['supplementalCredentials'] : 1, self.NAME_TO_INTERNAL['pekList'] : 1, + self.NAME_TO_INTERNAL['trustAuthIncoming'] : 1, + self.NAME_TO_INTERNAL['trustAuthOutgoing'] : 1, + self.NAME_TO_INTERNAL['trustPartner'] : 1, } def getResumeSessionFile(self): @@ -2105,6 +2117,77 @@ def __fileTimeToDateTime(t): dt = datetime.fromtimestamp(t) return dt.strftime("%Y-%m-%d %H:%M") + def __decryptTDOSecret(self, record, way, outputfile=None): + + LOG.debug('Entering NTDSHashes.__decryptTDOSecret') + trustAuthTypeList = ["NONE", "NT4OWF","CLEAR","VERSION"] + if way == "In": + val = record[self.NAME_TO_INTERNAL['trustAuthIncoming']] + else: + val = record[self.NAME_TO_INTERNAL['trustAuthOutgoing']] + cipherText = self.CRYPTED_BLOB(unhexlify(val)) + plaintext = None + if cipherText['Header'][:4] == b'\x13\x00\x00\x00': + # Win2016 TP4 decryption is different + pekIndex = hexlify(cipherText['Header']) + plainText = self.__cryptoCommon.decryptAES(self.__PEK[int(pekIndex[8:10])], + cipherText['EncryptedHash'][4:], + cipherText['KeyMaterial']) + haveInfo = True + else: + plainText = self.__removeRC4Layer(cipherText) + haveInfo = True + tdoInfo = plainText.hex() + authInfoOffset = unpack(' 0: + if self.__useVSSMethod is True: + LOG.info('TDO secrets from %s ' % self.__NTDS) + else: + LOG.info('TDO extraction only available with NTDS local dumpfile') + + for itemKey in list(self.__tdoSecrets.keys()): + self.__perSecretCallback(NTDSHashes.SECRET_TYPE.NTDS, itemKey) + finally: # Resources cleanup if hashesOutputFile is not None: @@ -2742,6 +2848,9 @@ def dump(self): if clearTextOutputFile is not None: clearTextOutputFile.close() + if tdoOutputFile is not None: + tdoOutputFile.close() + self.__resumeSession.endTransaction() @classmethod