def __init__(self, ntdsFile, bootKey, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None): self.__bootKey = bootKey self.__logger = logger self.__NTDS = ntdsFile self.__history = history self.__noLMHash = noLMHash self.__useVSSMethod = useVSSMethod self.__remoteOps = remoteOps self.__pwdLastSet = pwdLastSet if self.__NTDS is not None: self.__ESEDB = ESENT_DB(ntdsFile, isRemote=isRemote) self.__cursor = self.__ESEDB.openTable('datatable') self.__tmpUsers = list() self.__PEK = list() self.__cryptoCommon = CryptoCommon() self.__kerberosKeys = OrderedDict() self.__clearTextPwds = OrderedDict() self.__justNTLM = justNTLM self.__savedSessionFile = resumeSession self.__resumeSessionFile = None self.__outputFileName = outputFileName
def __init__(self, securityFile, bootKey, logger, remoteOps = None, isRemote = False): OfflineRegistry.__init__(self,securityFile, isRemote) self.__hashedBootKey = '' self.__bootKey = bootKey self.__LSAKey = '' self.__NKLMKey = '' self.__isRemote = isRemote self.__vistaStyle = True self.__cryptoCommon = CryptoCommon() self.__securityFile = securityFile self.__logger = logger self.__remoteOps = remoteOps self.__cachedItems = [] self.__secretItems = []
def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote=False): OfflineRegistry.__init__(self, samFile, isRemote) self.__samFile = samFile self.__hashedBootKey = '' self.__bootKey = bootKey self.__logger = logger self.__db = db self.__host = host self.__hostname = hostname self.__cryptoCommon = CryptoCommon() self.__itemsFound = {}
def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote = False): OfflineRegistry.__init__(self, samFile, isRemote) self.__samFile = samFile self.__hashedBootKey = '' self.__bootKey = bootKey self.__logger = logger self.__db = db self.__host = host self.__hostname = hostname self.__cryptoCommon = CryptoCommon() self.__itemsFound = {}
def __init__(self, ntdsFile, bootKey, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None): self.__bootKey = bootKey self.__logger = logger self.__NTDS = ntdsFile self.__history = history self.__noLMHash = noLMHash self.__useVSSMethod = useVSSMethod self.__remoteOps = remoteOps self.__pwdLastSet = pwdLastSet if self.__NTDS is not None: self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote) self.__cursor = self.__ESEDB.openTable('datatable') self.__tmpUsers = list() self.__PEK = list() self.__cryptoCommon = CryptoCommon() self.__kerberosKeys = OrderedDict() self.__clearTextPwds = OrderedDict() self.__justNTLM = justNTLM self.__savedSessionFile = resumeSession self.__resumeSessionFile = None self.__outputFileName = outputFileName
class NTDSHashes: NAME_TO_INTERNAL = { 'uSNCreated':'ATTq131091', 'uSNChanged':'ATTq131192', 'name':'ATTm3', 'objectGUID':'ATTk589826', 'objectSid':'ATTr589970', 'userAccountControl':'ATTj589832', 'primaryGroupID':'ATTj589922', 'accountExpires':'ATTq589983', 'logonCount':'ATTj589993', 'sAMAccountName':'ATTm590045', 'sAMAccountType':'ATTj590126', 'lastLogonTimestamp':'ATTq589876', 'userPrincipalName':'ATTm590480', 'unicodePwd':'ATTk589914', 'dBCSPwd':'ATTk589879', 'ntPwdHistory':'ATTk589918', 'lmPwdHistory':'ATTk589984', 'pekList':'ATTk590689', 'supplementalCredentials':'ATTk589949', 'pwdLastSet':'ATTq589920', } NAME_TO_ATTRTYP = { 'userPrincipalName': 0x90290, 'sAMAccountName': 0x900DD, 'unicodePwd': 0x9005A, 'dBCSPwd': 0x90037, 'ntPwdHistory': 0x9005E, 'lmPwdHistory': 0x900A0, 'supplementalCredentials': 0x9007D, 'objectSid': 0x90092, } ATTRTYP_TO_ATTID = { 'userPrincipalName': '1.2.840.113556.1.4.656', 'sAMAccountName': '1.2.840.113556.1.4.221', 'unicodePwd': '1.2.840.113556.1.4.90', 'dBCSPwd': '1.2.840.113556.1.4.55', 'ntPwdHistory': '1.2.840.113556.1.4.94', 'lmPwdHistory': '1.2.840.113556.1.4.160', 'supplementalCredentials': '1.2.840.113556.1.4.125', 'objectSid': '1.2.840.113556.1.4.146', 'pwdLastSet': '1.2.840.113556.1.4.96', } KERBEROS_TYPE = { 1:'dec-cbc-crc', 3:'des-cbc-md5', 17:'aes128-cts-hmac-sha1-96', 18:'aes256-cts-hmac-sha1-96', 0xffffff74:'rc4_hmac', } INTERNAL_TO_NAME = dict((v,k) for k,v in NAME_TO_INTERNAL.iteritems()) SAM_NORMAL_USER_ACCOUNT = 0x30000000 SAM_MACHINE_ACCOUNT = 0x30000001 SAM_TRUST_ACCOUNT = 0x30000002 ACCOUNT_TYPES = ( SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT) class PEKLIST_ENC(Structure): structure = ( ('Header','8s=""'), ('KeyMaterial','16s=""'), ('EncryptedPek',':'), ) class PEKLIST_PLAIN(Structure): structure = ( ('Header','32s=""'), ('DecryptedPek',':'), ) class PEK_KEY(Structure): structure = ( ('Header','1s=""'), ('Padding','3s=""'), ('Key','16s=""'), ) class CRYPTED_HASH(Structure): structure = ( ('Header','8s=""'), ('KeyMaterial','16s=""'), ('EncryptedHash','16s=""'), ) class CRYPTED_HISTORY(Structure): structure = ( ('Header','8s=""'), ('KeyMaterial','16s=""'), ('EncryptedHash',':'), ) class CRYPTED_BLOB(Structure): structure = ( ('Header','8s=""'), ('KeyMaterial','16s=""'), ('EncryptedHash',':'), ) def __init__(self, ntdsFile, bootKey, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None): self.__bootKey = bootKey self.__logger = logger self.__NTDS = ntdsFile self.__history = history self.__noLMHash = noLMHash self.__useVSSMethod = useVSSMethod self.__remoteOps = remoteOps self.__pwdLastSet = pwdLastSet if self.__NTDS is not None: self.__ESEDB = ESENT_DB(ntdsFile, isRemote = isRemote) self.__cursor = self.__ESEDB.openTable('datatable') self.__tmpUsers = list() self.__PEK = list() self.__cryptoCommon = CryptoCommon() self.__kerberosKeys = OrderedDict() self.__clearTextPwds = OrderedDict() self.__justNTLM = justNTLM self.__savedSessionFile = resumeSession self.__resumeSessionFile = None self.__outputFileName = outputFileName def getResumeSessionFile(self): return self.__resumeSessionFile def __getPek(self): logging.info('Searching for pekList, be patient') peklist = None while True: record = self.__ESEDB.getNextRow(self.__cursor) if record is None: break elif record[self.NAME_TO_INTERNAL['pekList']] is not None: peklist = unhexlify(record[self.NAME_TO_INTERNAL['pekList']]) break elif record[self.NAME_TO_INTERNAL['sAMAccountType']] in self.ACCOUNT_TYPES: # Okey.. we found some users, but we're not yet ready to process them. # Let's just store them in a temp list self.__tmpUsers.append(record) if peklist is not None: encryptedPekList = self.PEKLIST_ENC(peklist) md5 = hashlib.new('md5') md5.update(self.__bootKey) for i in range(1000): md5.update(encryptedPekList['KeyMaterial']) tmpKey = md5.digest() rc4 = ARC4.new(tmpKey) decryptedPekList = self.PEKLIST_PLAIN(rc4.encrypt(encryptedPekList['EncryptedPek'])) PEKLen = len(self.PEK_KEY()) for i in range(len( decryptedPekList['DecryptedPek'] ) / PEKLen ): cursor = i * PEKLen pek = self.PEK_KEY(decryptedPekList['DecryptedPek'][cursor:cursor+PEKLen]) logging.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key'])) self.__PEK.append(pek['Key']) def __removeRC4Layer(self, cryptedHash): md5 = hashlib.new('md5') # PEK index can be found on header of each ciphered blob (pos 8-10) pekIndex = hexlify(cryptedHash['Header']) md5.update(self.__PEK[int(pekIndex[8:10])]) md5.update(cryptedHash['KeyMaterial']) tmpKey = md5.digest() rc4 = ARC4.new(tmpKey) plainText = rc4.encrypt(cryptedHash['EncryptedHash']) return plainText def __removeDESLayer(self, cryptedHash, rid): Key1,Key2 = self.__cryptoCommon.deriveKey(int(rid)) Crypt1 = DES.new(Key1, DES.MODE_ECB) Crypt2 = DES.new(Key2, DES.MODE_ECB) decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt(cryptedHash[8:]) return decryptedHash def __fileTimeToDateTime(self, t): t -= 116444736000000000 t /= 10000000 if t < 0: return 'never' else: dt = datetime.fromtimestamp(t) return dt.strftime("%Y-%m-%d %H:%M") def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=None): # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures haveInfo = False if self.__useVSSMethod is True: if record[self.NAME_TO_INTERNAL['supplementalCredentials']] is not None: if len(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) > 24: if record[self.NAME_TO_INTERNAL['userPrincipalName']] is not None: domain = record[self.NAME_TO_INTERNAL['userPrincipalName']].split('@')[-1] userName = '******' % (domain, record[self.NAME_TO_INTERNAL['sAMAccountName']]) else: userName = '******' % record[self.NAME_TO_INTERNAL['sAMAccountName']] cipherText = self.CRYPTED_BLOB(unhexlify(record[self.NAME_TO_INTERNAL['supplementalCredentials']])) plainText = self.__removeRC4Layer(cipherText) haveInfo = True else: domain = None userName = None for attr in record['pmsgOut']['V6']['pObjects']['Entinf']['AttrBlock']['pAttr']: try: attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) LOOKUP_TABLE = self.ATTRTYP_TO_ATTID except Exception, e: logging.debug('Failed to execute OidFromAttid with error %s' % e) # Fallbacking to fixed table and hope for the best attId = attr['attrTyp'] LOOKUP_TABLE = self.NAME_TO_ATTRTYP if attId == LOOKUP_TABLE['userPrincipalName']: if attr['AttrVal']['valCount'] > 0: try: domain = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le').split('@')[-1] except: domain = None else: domain = None elif attId == LOOKUP_TABLE['sAMAccountName']: if attr['AttrVal']['valCount'] > 0: try: userName = ''.join(attr['AttrVal']['pAVal'][0]['pVal']).decode('utf-16le') except: logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1]) userName = '******' else: logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1]) userName = '******' if attId == LOOKUP_TABLE['supplementalCredentials']: if attr['AttrVal']['valCount'] > 0: blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) plainText = drsuapi.DecryptAttributeValue(self.__remoteOps.getDrsr(), blob) if len(plainText) > 24: haveInfo = True if domain is not None: userName = '******' % (domain, userName) if haveInfo is True: try: userProperties = samr.USER_PROPERTIES(plainText) except: # On some old w2k3 there might be user properties that don't # match [MS-SAMR] structure, discarding them return propertiesData = userProperties['UserProperties'] for propertyCount in range(userProperties['PropertyCount']): userProperty = samr.USER_PROPERTY(propertiesData) propertiesData = propertiesData[len(userProperty):] # For now, we will only process Newer Kerberos Keys and CLEARTEXT if userProperty['PropertyName'].decode('utf-16le') == 'Primary:Kerberos-Newer-Keys': propertyValueBuffer = unhexlify(userProperty['PropertyValue']) kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW(propertyValueBuffer) data = kerbStoredCredentialNew['Buffer'] for credential in range(kerbStoredCredentialNew['CredentialCount']): keyDataNew = samr.KERB_KEY_DATA_NEW(data) data = data[len(keyDataNew):] keyValue = propertyValueBuffer[keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']] if self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']): answer = "%s:%s:%s" % (userName, self.KERBEROS_TYPE[keyDataNew['KeyType']],hexlify(keyValue)) else: answer = "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']),hexlify(keyValue)) # We're just storing the keys, not printing them, to make the output more readable # This is kind of ugly... but it's what I came up with tonight to get an ordered # set :P. Better ideas welcomed ;) self.__kerberosKeys[answer] = None if keysFile is not None: self.__writeOutput(keysFile, answer + '\n') elif userProperty['PropertyName'].decode('utf-16le') == 'Primary:CLEARTEXT': # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password. answer = "%s:CLEARTEXT:%s" % (userName, unhexlify(userProperty['PropertyValue']).decode('utf-16le')) self.__clearTextPwds[answer] = None if clearTextFile is not None: self.__writeOutput(clearTextFile, answer + '\n') if clearTextFile is not None: clearTextFile.flush() if keysFile is not None: keysFile.flush()
class SAMHashes(OfflineRegistry): def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote = False): OfflineRegistry.__init__(self, samFile, isRemote) self.__samFile = samFile self.__hashedBootKey = '' self.__bootKey = bootKey self.__logger = logger self.__db = db self.__host = host self.__hostname = hostname self.__cryptoCommon = CryptoCommon() self.__itemsFound = {} def MD5(self, data): md5 = hashlib.new('md5') md5.update(data) return md5.digest() def getHBootKey(self): logging.debug('Calculating HashedBootKey from SAM') QWERTY = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" DIGITS = "0123456789012345678901234567890123456789\0" F = self.getValue(ntpath.join('SAM\Domains\Account','F'))[1] domainData = DOMAIN_ACCOUNT_F(F) rc4Key = self.MD5(domainData['Key0']['Salt'] + QWERTY + self.__bootKey + DIGITS) rc4 = ARC4.new(rc4Key) self.__hashedBootKey = rc4.encrypt(domainData['Key0']['Key']+domainData['Key0']['CheckSum']) # Verify key with checksum checkSum = self.MD5( self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY) if checkSum != self.__hashedBootKey[16:]: raise Exception('hashedBootKey CheckSum failed, Syskey startup password probably in use! :(') def __decryptHash(self, rid, cryptedHash, constant): # Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key # plus hashedBootKey stuff Key1,Key2 = self.__cryptoCommon.deriveKey(rid) Crypt1 = DES.new(Key1, DES.MODE_ECB) Crypt2 = DES.new(Key2, DES.MODE_ECB) rc4Key = self.MD5( self.__hashedBootKey[:0x10] + pack("<L",rid) + constant ) rc4 = ARC4.new(rc4Key) key = rc4.encrypt(cryptedHash) decryptedHash = Crypt1.decrypt(key[:8]) + Crypt2.decrypt(key[8:]) return decryptedHash def dump(self): NTPASSWORD = "******" LMPASSWORD = "******" if self.__samFile is None: # No SAM file provided return self.__logger.success('Dumping local SAM hashes (uid:rid:lmhash:nthash)') self.getHBootKey() usersKey = 'SAM\\Domains\\Account\\Users' # Enumerate all the RIDs rids = self.enumKey(usersKey) # Remove the Names item try: rids.remove('Names') except: pass for rid in rids: 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['LMHashLength'] == 20: encLMHash = V[userAccount['LMHashOffset']+4:userAccount['LMHashOffset']+userAccount['LMHashLength']] else: encLMHash = '' if userAccount['NTHashLength'] == 20: encNTHash = V[userAccount['NTHashOffset']+4:userAccount['NTHashOffset']+userAccount['NTHashLength']] else: encNTHash = '' lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD) ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD) if lmHash == '': lmHash = ntlm.LMOWFv1('','') if ntHash == '': ntHash = ntlm.NTOWFv1('','') answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash)) self.__itemsFound[rid] = answer self.__logger.highlight(answer) self.__db.add_credential('hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash))) def export(self, fileName): if len(self.__itemsFound) > 0: items = sorted(self.__itemsFound) fd = codecs.open(fileName+'.sam','w+', encoding='utf-8') for item in items: fd.write(self.__itemsFound[item]+'\n') fd.close()
class NTDSHashes: NAME_TO_INTERNAL = { 'uSNCreated': 'ATTq131091', 'uSNChanged': 'ATTq131192', 'name': 'ATTm3', 'objectGUID': 'ATTk589826', 'objectSid': 'ATTr589970', 'userAccountControl': 'ATTj589832', 'primaryGroupID': 'ATTj589922', 'accountExpires': 'ATTq589983', 'logonCount': 'ATTj589993', 'sAMAccountName': 'ATTm590045', 'sAMAccountType': 'ATTj590126', 'lastLogonTimestamp': 'ATTq589876', 'userPrincipalName': 'ATTm590480', 'unicodePwd': 'ATTk589914', 'dBCSPwd': 'ATTk589879', 'ntPwdHistory': 'ATTk589918', 'lmPwdHistory': 'ATTk589984', 'pekList': 'ATTk590689', 'supplementalCredentials': 'ATTk589949', 'pwdLastSet': 'ATTq589920', } NAME_TO_ATTRTYP = { 'userPrincipalName': 0x90290, 'sAMAccountName': 0x900DD, 'unicodePwd': 0x9005A, 'dBCSPwd': 0x90037, 'ntPwdHistory': 0x9005E, 'lmPwdHistory': 0x900A0, 'supplementalCredentials': 0x9007D, 'objectSid': 0x90092, } ATTRTYP_TO_ATTID = { 'userPrincipalName': '1.2.840.113556.1.4.656', 'sAMAccountName': '1.2.840.113556.1.4.221', 'unicodePwd': '1.2.840.113556.1.4.90', 'dBCSPwd': '1.2.840.113556.1.4.55', 'ntPwdHistory': '1.2.840.113556.1.4.94', 'lmPwdHistory': '1.2.840.113556.1.4.160', 'supplementalCredentials': '1.2.840.113556.1.4.125', 'objectSid': '1.2.840.113556.1.4.146', 'pwdLastSet': '1.2.840.113556.1.4.96', } KERBEROS_TYPE = { 1: 'dec-cbc-crc', 3: 'des-cbc-md5', 17: 'aes128-cts-hmac-sha1-96', 18: 'aes256-cts-hmac-sha1-96', 0xffffff74: 'rc4_hmac', } INTERNAL_TO_NAME = dict((v, k) for k, v in NAME_TO_INTERNAL.iteritems()) SAM_NORMAL_USER_ACCOUNT = 0x30000000 SAM_MACHINE_ACCOUNT = 0x30000001 SAM_TRUST_ACCOUNT = 0x30000002 ACCOUNT_TYPES = (SAM_NORMAL_USER_ACCOUNT, SAM_MACHINE_ACCOUNT, SAM_TRUST_ACCOUNT) class PEKLIST_ENC(Structure): structure = ( ('Header', '8s=""'), ('KeyMaterial', '16s=""'), ('EncryptedPek', ':'), ) class PEKLIST_PLAIN(Structure): structure = ( ('Header', '32s=""'), ('DecryptedPek', ':'), ) class PEK_KEY(Structure): structure = ( ('Header', '1s=""'), ('Padding', '3s=""'), ('Key', '16s=""'), ) class CRYPTED_HASH(Structure): structure = ( ('Header', '8s=""'), ('KeyMaterial', '16s=""'), ('EncryptedHash', '16s=""'), ) class CRYPTED_HISTORY(Structure): structure = ( ('Header', '8s=""'), ('KeyMaterial', '16s=""'), ('EncryptedHash', ':'), ) class CRYPTED_BLOB(Structure): structure = ( ('Header', '8s=""'), ('KeyMaterial', '16s=""'), ('EncryptedHash', ':'), ) def __init__(self, ntdsFile, bootKey, logger, isRemote=False, history=False, noLMHash=True, remoteOps=None, useVSSMethod=False, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=None): self.__bootKey = bootKey self.__logger = logger self.__NTDS = ntdsFile self.__history = history self.__noLMHash = noLMHash self.__useVSSMethod = useVSSMethod self.__remoteOps = remoteOps self.__pwdLastSet = pwdLastSet if self.__NTDS is not None: self.__ESEDB = ESENT_DB(ntdsFile, isRemote=isRemote) self.__cursor = self.__ESEDB.openTable('datatable') self.__tmpUsers = list() self.__PEK = list() self.__cryptoCommon = CryptoCommon() self.__kerberosKeys = OrderedDict() self.__clearTextPwds = OrderedDict() self.__justNTLM = justNTLM self.__savedSessionFile = resumeSession self.__resumeSessionFile = None self.__outputFileName = outputFileName def getResumeSessionFile(self): return self.__resumeSessionFile def __getPek(self): logging.info('Searching for pekList, be patient') peklist = None while True: record = self.__ESEDB.getNextRow(self.__cursor) if record is None: break elif record[self.NAME_TO_INTERNAL['pekList']] is not None: peklist = unhexlify(record[self.NAME_TO_INTERNAL['pekList']]) break elif record[self.NAME_TO_INTERNAL[ 'sAMAccountType']] in self.ACCOUNT_TYPES: # Okey.. we found some users, but we're not yet ready to process them. # Let's just store them in a temp list self.__tmpUsers.append(record) if peklist is not None: encryptedPekList = self.PEKLIST_ENC(peklist) md5 = hashlib.new('md5') md5.update(self.__bootKey) for i in range(1000): md5.update(encryptedPekList['KeyMaterial']) tmpKey = md5.digest() rc4 = ARC4.new(tmpKey) decryptedPekList = self.PEKLIST_PLAIN( rc4.encrypt(encryptedPekList['EncryptedPek'])) PEKLen = len(self.PEK_KEY()) for i in range(len(decryptedPekList['DecryptedPek']) / PEKLen): cursor = i * PEKLen pek = self.PEK_KEY( decryptedPekList['DecryptedPek'][cursor:cursor + PEKLen]) logging.info("PEK # %d found and decrypted: %s", i, hexlify(pek['Key'])) self.__PEK.append(pek['Key']) def __removeRC4Layer(self, cryptedHash): md5 = hashlib.new('md5') # PEK index can be found on header of each ciphered blob (pos 8-10) pekIndex = hexlify(cryptedHash['Header']) md5.update(self.__PEK[int(pekIndex[8:10])]) md5.update(cryptedHash['KeyMaterial']) tmpKey = md5.digest() rc4 = ARC4.new(tmpKey) plainText = rc4.encrypt(cryptedHash['EncryptedHash']) return plainText def __removeDESLayer(self, cryptedHash, rid): Key1, Key2 = self.__cryptoCommon.deriveKey(int(rid)) Crypt1 = DES.new(Key1, DES.MODE_ECB) Crypt2 = DES.new(Key2, DES.MODE_ECB) decryptedHash = Crypt1.decrypt(cryptedHash[:8]) + Crypt2.decrypt( cryptedHash[8:]) return decryptedHash def __fileTimeToDateTime(self, t): t -= 116444736000000000 t /= 10000000 if t < 0: return 'never' else: dt = datetime.fromtimestamp(t) return dt.strftime("%Y-%m-%d %H:%M") def __decryptSupplementalInfo(self, record, prefixTable=None, keysFile=None, clearTextFile=None): # This is based on [MS-SAMR] 2.2.10 Supplemental Credentials Structures haveInfo = False if self.__useVSSMethod is True: if record[self. NAME_TO_INTERNAL['supplementalCredentials']] is not None: if len( unhexlify(record[self.NAME_TO_INTERNAL[ 'supplementalCredentials']])) > 24: if record[self.NAME_TO_INTERNAL[ 'userPrincipalName']] is not None: domain = record[self.NAME_TO_INTERNAL[ 'userPrincipalName']].split('@')[-1] userName = '******' % (domain, record[ self.NAME_TO_INTERNAL['sAMAccountName']]) else: userName = '******' % record[ self.NAME_TO_INTERNAL['sAMAccountName']] cipherText = self.CRYPTED_BLOB( unhexlify(record[ self.NAME_TO_INTERNAL['supplementalCredentials']])) plainText = self.__removeRC4Layer(cipherText) haveInfo = True else: domain = None userName = None for attr in record['pmsgOut']['V6']['pObjects']['Entinf'][ 'AttrBlock']['pAttr']: try: attId = drsuapi.OidFromAttid(prefixTable, attr['attrTyp']) LOOKUP_TABLE = self.ATTRTYP_TO_ATTID except Exception, e: logging.debug( 'Failed to execute OidFromAttid with error %s' % e) # Fallbacking to fixed table and hope for the best attId = attr['attrTyp'] LOOKUP_TABLE = self.NAME_TO_ATTRTYP if attId == LOOKUP_TABLE['userPrincipalName']: if attr['AttrVal']['valCount'] > 0: try: domain = ''.join( attr['AttrVal']['pAVal'][0]['pVal']).decode( 'utf-16le').split('@')[-1] except: domain = None else: domain = None elif attId == LOOKUP_TABLE['sAMAccountName']: if attr['AttrVal']['valCount'] > 0: try: userName = ''.join(attr['AttrVal']['pAVal'][0] ['pVal']).decode('utf-16le') except: logging.error('Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC'] ['StringName'][:-1]) userName = '******' else: logging.error( 'Cannot get sAMAccountName for %s' % record['pmsgOut']['V6']['pNC']['StringName'][:-1]) userName = '******' if attId == LOOKUP_TABLE['supplementalCredentials']: if attr['AttrVal']['valCount'] > 0: blob = ''.join(attr['AttrVal']['pAVal'][0]['pVal']) plainText = drsuapi.DecryptAttributeValue( self.__remoteOps.getDrsr(), blob) if len(plainText) > 24: haveInfo = True if domain is not None: userName = '******' % (domain, userName) if haveInfo is True: try: userProperties = samr.USER_PROPERTIES(plainText) except: # On some old w2k3 there might be user properties that don't # match [MS-SAMR] structure, discarding them return propertiesData = userProperties['UserProperties'] for propertyCount in range(userProperties['PropertyCount']): userProperty = samr.USER_PROPERTY(propertiesData) propertiesData = propertiesData[len(userProperty):] # For now, we will only process Newer Kerberos Keys and CLEARTEXT if userProperty['PropertyName'].decode( 'utf-16le') == 'Primary:Kerberos-Newer-Keys': propertyValueBuffer = unhexlify( userProperty['PropertyValue']) kerbStoredCredentialNew = samr.KERB_STORED_CREDENTIAL_NEW( propertyValueBuffer) data = kerbStoredCredentialNew['Buffer'] for credential in range( kerbStoredCredentialNew['CredentialCount']): keyDataNew = samr.KERB_KEY_DATA_NEW(data) data = data[len(keyDataNew):] keyValue = propertyValueBuffer[ keyDataNew['KeyOffset']:][:keyDataNew['KeyLength']] if self.KERBEROS_TYPE.has_key(keyDataNew['KeyType']): answer = "%s:%s:%s" % ( userName, self.KERBEROS_TYPE[keyDataNew['KeyType']], hexlify(keyValue)) else: answer = "%s:%s:%s" % (userName, hex(keyDataNew['KeyType']), hexlify(keyValue)) # We're just storing the keys, not printing them, to make the output more readable # This is kind of ugly... but it's what I came up with tonight to get an ordered # set :P. Better ideas welcomed ;) self.__kerberosKeys[answer] = None if keysFile is not None: self.__writeOutput(keysFile, answer + '\n') elif userProperty['PropertyName'].decode( 'utf-16le') == 'Primary:CLEARTEXT': # [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property # This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password. answer = "%s:CLEARTEXT:%s" % ( userName, unhexlify( userProperty['PropertyValue']).decode('utf-16le')) self.__clearTextPwds[answer] = None if clearTextFile is not None: self.__writeOutput(clearTextFile, answer + '\n') if clearTextFile is not None: clearTextFile.flush() if keysFile is not None: keysFile.flush()
class SAMHashes(OfflineRegistry): def __init__(self, samFile, bootKey, logger, db, host, hostname, isRemote=False): OfflineRegistry.__init__(self, samFile, isRemote) self.__samFile = samFile self.__hashedBootKey = '' self.__bootKey = bootKey self.__logger = logger self.__db = db self.__host = host self.__hostname = hostname self.__cryptoCommon = CryptoCommon() self.__itemsFound = {} def MD5(self, data): md5 = hashlib.new('md5') md5.update(data) return md5.digest() def getHBootKey(self): logging.debug('Calculating HashedBootKey from SAM') QWERTY = "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\0" DIGITS = "0123456789012345678901234567890123456789\0" F = self.getValue(ntpath.join('SAM\Domains\Account', 'F'))[1] domainData = DOMAIN_ACCOUNT_F(F) rc4Key = self.MD5(domainData['Key0']['Salt'] + QWERTY + self.__bootKey + DIGITS) rc4 = ARC4.new(rc4Key) self.__hashedBootKey = rc4.encrypt(domainData['Key0']['Key'] + domainData['Key0']['CheckSum']) # Verify key with checksum checkSum = self.MD5(self.__hashedBootKey[:16] + DIGITS + self.__hashedBootKey[:16] + QWERTY) if checkSum != self.__hashedBootKey[16:]: raise Exception( 'hashedBootKey CheckSum failed, Syskey startup password probably in use! :(' ) def __decryptHash(self, rid, cryptedHash, constant): # Section 2.2.11.1.1 Encrypting an NT or LM Hash Value with a Specified Key # plus hashedBootKey stuff Key1, Key2 = self.__cryptoCommon.deriveKey(rid) Crypt1 = DES.new(Key1, DES.MODE_ECB) Crypt2 = DES.new(Key2, DES.MODE_ECB) rc4Key = self.MD5(self.__hashedBootKey[:0x10] + pack("<L", rid) + constant) rc4 = ARC4.new(rc4Key) key = rc4.encrypt(cryptedHash) decryptedHash = Crypt1.decrypt(key[:8]) + Crypt2.decrypt(key[8:]) return decryptedHash def dump(self): NTPASSWORD = "******" LMPASSWORD = "******" if self.__samFile is None: # No SAM file provided return self.__logger.success( 'Dumping local SAM hashes (uid:rid:lmhash:nthash)') self.getHBootKey() usersKey = 'SAM\\Domains\\Account\\Users' # Enumerate all the RIDs rids = self.enumKey(usersKey) # Remove the Names item try: rids.remove('Names') except: pass for rid in rids: 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['LMHashLength'] == 20: encLMHash = V[userAccount['LMHashOffset'] + 4:userAccount['LMHashOffset'] + userAccount['LMHashLength']] else: encLMHash = '' if userAccount['NTHashLength'] == 20: encNTHash = V[userAccount['NTHashOffset'] + 4:userAccount['NTHashOffset'] + userAccount['NTHashLength']] else: encNTHash = '' lmHash = self.__decryptHash(rid, encLMHash, LMPASSWORD) ntHash = self.__decryptHash(rid, encNTHash, NTPASSWORD) if lmHash == '': lmHash = ntlm.LMOWFv1('', '') if ntHash == '': ntHash = ntlm.NTOWFv1('', '') answer = "%s:%d:%s:%s:::" % (userName, rid, hexlify(lmHash), hexlify(ntHash)) self.__itemsFound[rid] = answer self.__logger.highlight(answer) self.__db.add_credential( 'hash', self.__hostname, userName, '{}:{}'.format(hexlify(lmHash), hexlify(ntHash))) def export(self, fileName): if len(self.__itemsFound) > 0: items = sorted(self.__itemsFound) fd = codecs.open(fileName + '.sam', 'w+', encoding='utf-8') for item in items: fd.write(self.__itemsFound[item] + '\n') fd.close()
class LSASecrets(OfflineRegistry): def __init__(self, securityFile, bootKey, logger, remoteOps = None, isRemote = False): OfflineRegistry.__init__(self,securityFile, isRemote) self.__hashedBootKey = '' self.__bootKey = bootKey self.__LSAKey = '' self.__NKLMKey = '' self.__isRemote = isRemote self.__vistaStyle = True self.__cryptoCommon = CryptoCommon() self.__securityFile = securityFile self.__logger = logger self.__remoteOps = remoteOps self.__cachedItems = [] self.__secretItems = [] def MD5(self, data): md5 = hashlib.new('md5') md5.update(data) return md5.digest() def __sha256(self, key, value, rounds=1000): sha = hashlib.sha256() sha.update(key) for i in range(1000): sha.update(value) return sha.digest() def __decryptAES(self, key, value, iv='\x00'*16): plainText = '' if iv != '\x00'*16: aes256 = AES.new(key,AES.MODE_CBC, iv) for index in range(0, len(value), 16): if iv == '\x00'*16: aes256 = AES.new(key,AES.MODE_CBC, iv) cipherBuffer = value[index:index+16] # Pad buffer to 16 bytes if len(cipherBuffer) < 16: cipherBuffer += '\x00' * (16-len(cipherBuffer)) plainText += aes256.decrypt(cipherBuffer) return plainText def __decryptSecret(self, key, value): # [MS-LSAD] Section 5.1.2 plainText = '' encryptedSecretSize = unpack('<I', value[:4])[0] value = value[len(value)-encryptedSecretSize:] key0 = key for i in range(0, len(value), 8): cipherText = value[:8] tmpStrKey = key0[:7] tmpKey = self.__cryptoCommon.transformKey(tmpStrKey) Crypt1 = DES.new(tmpKey, DES.MODE_ECB) plainText += Crypt1.decrypt(cipherText) key0 = key0[7:] value = value[8:] # AdvanceKey if len(key0) < 7: key0 = key[len(key0):] secret = LSA_SECRET_XP(plainText) return secret['Secret'] def __decryptHash(self, key, value, iv): hmac_md5 = HMAC.new(key,iv) rc4key = hmac_md5.digest() rc4 = ARC4.new(rc4key) data = rc4.encrypt(value) return data def __decryptLSA(self, value): if self.__vistaStyle is True: # ToDo: There could be more than one LSA Keys record = LSA_SECRET(value) tmpKey = self.__sha256(self.__bootKey, record['EncryptedData'][:32]) plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:]) record = LSA_SECRET_BLOB(plainText) self.__LSAKey = record['Secret'][52:][:32] else: md5 = hashlib.new('md5') md5.update(self.__bootKey) for i in range(1000): md5.update(value[60:76]) tmpKey = md5.digest() rc4 = ARC4.new(tmpKey) plainText = rc4.decrypt(value[12:60]) self.__LSAKey = plainText[0x10:0x20] def __getLSASecretKey(self): logging.debug('Decrypting LSA Key') # Let's try the key post XP value = self.getValue('\\Policy\\PolEKList\\default') if value is None: logging.debug('PolEKList not found, trying PolSecretEncryptionKey') # Second chance value = self.getValue('\\Policy\\PolSecretEncryptionKey\\default') self.__vistaStyle = False if value is None: # No way :( return None self.__decryptLSA(value[1]) def __getNLKMSecret(self): logging.debug('Decrypting NL$KM') value = self.getValue('\\Policy\\Secrets\\NL$KM\\CurrVal\\default') if value is None: raise Exception("Couldn't get NL$KM value") if self.__vistaStyle is True: record = LSA_SECRET(value[1]) tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) self.__NKLMKey = self.__decryptAES(tmpKey, record['EncryptedData'][32:]) else: self.__NKLMKey = self.__decryptSecret(self.__LSAKey, value[1]) def __pad(self, data): if (data & 0x3) > 0: return data + (data & 0x3) else: return data def dumpCachedHashes(self): if self.__securityFile is None: # No SECURITY file provided return self.__logger.success('Dumping cached domain logon information (uid:encryptedHash:longDomain:domain)') # Let's first see if there are cached entries values = self.enumValues('\\Cache') if values is None: # No cache entries return try: # Remove unnecesary value values.remove('NL$Control') except: pass self.__getLSASecretKey() self.__getNLKMSecret() for value in values: logging.debug('Looking into %s' % value) record = NL_RECORD(self.getValue(ntpath.join('\\Cache',value))[1]) if record['CH'] != 16 * '\x00': if self.__vistaStyle is True: plainText = self.__decryptAES(self.__NKLMKey[16:32], record['EncryptedData'], record['CH']) else: plainText = self.__decryptHash(self.__NKLMKey, record['EncryptedData'], record['CH']) pass encHash = plainText[:0x10] plainText = plainText[0x48:] userName = plainText[:record['UserLength']].decode('utf-16le') plainText = plainText[self.__pad(record['UserLength']):] domain = plainText[:record['DomainNameLength']].decode('utf-16le') plainText = plainText[self.__pad(record['DomainNameLength']):] domainLong = plainText[:self.__pad(record['FullDomainLength'])].decode('utf-16le') answer = "%s:%s:%s:%s:::" % (userName, hexlify(encHash), domainLong, domain) self.__cachedItems.append(answer) self.__logger.highlight(answer) def __printSecret(self, name, secretItem): # Based on [MS-LSAD] section 3.1.1.4 # First off, let's discard NULL secrets. if len(secretItem) == 0: logging.debug('Discarding secret %s, NULL Data' % name) return # We might have secrets with zero if secretItem.startswith('\x00\x00'): logging.debug('Discarding secret %s, all zeros' % name) return upperName = name.upper() logging.info('%s ' % name) secret = '' if upperName.startswith('_SC_'): # Service name, a password might be there # Let's first try to decode the secret try: strDecoded = secretItem.decode('utf-16le') except: pass else: # We have to get the account the service # runs under if self.__isRemote is True: account = self.__remoteOps.getServiceAccount(name[4:]) if account is None: secret = '(Unknown User):' else: secret = "%s:" % account else: # We don't support getting this info for local targets at the moment secret = '(Unknown User):' secret += strDecoded elif upperName.startswith('DEFAULTPASSWORD'): # defaults password for winlogon # Let's first try to decode the secret try: strDecoded = secretItem.decode('utf-16le') except: pass else: # We have to get the account this password is for if self.__isRemote is True: account = self.__remoteOps.getDefaultLoginAccount() if account is None: secret = '(Unknown User):' else: secret = "%s:" % account else: # We don't support getting this info for local targets at the moment secret = '(Unknown User):' secret += strDecoded elif upperName.startswith('ASPNET_WP_PASSWORD'): try: strDecoded = secretItem.decode('utf-16le') except: pass else: secret = 'ASPNET: %s' % strDecoded elif upperName.startswith('$MACHINE.ACC'): # compute MD4 of the secret.. yes.. that is the nthash? :-o md4 = MD4.new() md4.update(secretItem) if self.__isRemote is True: machine, domain = self.__remoteOps.getMachineNameAndDomain() secret = "%s\\%s$:%s:%s:::" % (domain, machine, hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest())) else: secret = "$MACHINE.ACC: %s:%s" % (hexlify(ntlm.LMOWFv1('','')), hexlify(md4.digest())) if secret != '': self.__secretItems.append(secret) self.__logger.highlight(secret) else: # Default print, hexdump self.__secretItems.append('%s:%s' % (name, hexlify(secretItem))) self.__logger.highlight('{}:{}'.format(name, hexlify(secretItem))) #hexdump(secretItem) def dumpSecrets(self): if self.__securityFile is None: # No SECURITY file provided return self.__logger.success('Dumping LSA Secrets') # Let's first see if there are cached entries keys = self.enumKey('\\Policy\\Secrets') if keys is None: # No entries return try: # Remove unnecesary value keys.remove('NL$Control') except: pass if self.__LSAKey == '': self.__getLSASecretKey() for key in keys: logging.debug('Looking into %s' % key) value = self.getValue('\\Policy\\Secrets\\%s\\CurrVal\\default' % key) if value is not None: if self.__vistaStyle is True: record = LSA_SECRET(value[1]) tmpKey = self.__sha256(self.__LSAKey, record['EncryptedData'][:32]) plainText = self.__decryptAES(tmpKey, record['EncryptedData'][32:]) record = LSA_SECRET_BLOB(plainText) secret = record['Secret'] else: secret = self.__decryptSecret(self.__LSAKey, value[1]) self.__printSecret(key, secret) def exportSecrets(self, fileName): if len(self.__secretItems) > 0: fd = codecs.open(fileName+'.secrets','w+', encoding='utf-8') for item in self.__secretItems: fd.write(item+'\n') fd.close() def exportCached(self, fileName): if len(self.__cachedItems) > 0: fd = codecs.open(fileName+'.cached','w+', encoding='utf-8') for item in self.__cachedItems: fd.write(item+'\n') fd.close()